Skip to content

Automation

How this repository looks after itself: its 42 workflows of GitHub Actions, in .github/workflows/, check every change, make a release of what reaches main, answer issues and pull requests, look for vulnerabilities and keep the repository current. The website writes this page from the workflows at every build, with the comment at the top of each, so it is always as current as they are.

---
config:
  flowchart:
    wrappingWidth: 400
---
flowchart TB
    accTitle: How a repository looks after itself
    accDescr: copier copy makes a repository from the blueprint. Every change, yours or a bot's, is a pull request that merges once every check passes, and the release bot makes a signed release of what reaches main. Renovate, the blueprint bot and the settings bot keep the repository current on their own.

    blueprint[("Repo Blueprint")]
    you(["You"])
    renovate(["Renovate<br>every 2 hours"])
    updater(["Blueprint bot<br>after every release<br>and every week"])
    settings(["Settings bot<br>every week"])

    subgraph repo ["Your repository"]
        pr["Pull request"] --> checks{"Every check<br>passes?"}
        checks -- "yes" --> main["main"]
        main -- "release bot" --> release["Signed release"]
    end

    blueprint -- "copier copy" --> repo
    blueprint -. "a new release" .-> updater
    you -- "a change" --> pr
    renovate -- "updates" --> pr
    updater -- "copier update" --> pr
    settings -- "the settings as code" --> repo

    classDef bot fill:#526cfe2e,stroke:#526cfe,stroke-width:2px
    classDef person fill:#f59e0b2e,stroke:#f59e0b,stroke-width:2px
    classDef done fill:#16a34a2e,stroke:#16a34a,stroke-width:2px
    class renovate,updater,settings bot
    class you person
    class release done

What a pull request needs

A pull request merges into main once each of these checks passes, as the settings in .github/repository.toml require:

At a glance

The times are UTC. GitHub starts a scheduled run when it can: often some minutes late, and at busy times not at all.

Workflow When
CI (python) every push to main, every pull request, by hand
Diagrams every push to main that changes certain files, every Tuesday at 04:45 UTC, by hand
Docs every push to main, every pull request, by hand
Downstream every pull request that changes certain files, every Wednesday at 05:40 UTC, by hand
Links every push to main that changes certain files, every pull request that changes certain files, every Monday at 05:50 UTC, by hand
Lint every pull request, by hand
Mutation every Saturday at 01:15 UTC, by hand, every pull request that changes certain files
Newest Python every push to main, every Tuesday at 03:25 UTC, by hand
Next runner every Thursday at 05:15 UTC, by hand
Pull request title every pull request
Variants every pull request, every Wednesday at 05:10 UTC, by hand, when another workflow calls it
Windows every pull request that changes certain files, every Thursday at 03:20 UTC, by hand
Build the release when another workflow calls it
Release every push to main, every day at 05:25 UTC, by hand
Rollout after a run of Release, by hand
SBOM every Wednesday at 04:47 UTC, by hand
Release verification when another workflow calls it, every Wednesday at 05:17 UTC, by hand
After the checks after 16 workflows
Area labels every pull request
Dependabot maintenance every pull request
Docs pictures after a run of Release, by hand
Issue assistant new and changed issues, comments, by hand
Issue lifecycle every day at 06:41 UTC, every pull request, a release, after 2 workflows, by hand
Renovate every 2 hours at :17, every push to main that changes certain files, every pull request that changes certain files, by hand
Screenshots every push to main, after a run of Release, every Friday at 04:29 UTC, by hand
Issue assistant sync every day at 05:20 UTC, by hand, every pull request that changes certain files
Update branches every push to main, every pull request, after 15 workflows, every 6 hours at :10, by hand
Welcome every pull request
CodeQL every push to main, every pull request, every Wednesday at 04:17 UTC, by hand
Dependency review every pull request, by hand
Findings every day at 07:17 UTC, every push to main that changes certain files, after 4 workflows, by hand
Fuzzing every pull request that changes certain files, every Sunday at 03:23 UTC, by hand
OSV-Scanner every pull request that changes certain files, every push to main, every Monday at 05:33 UTC, by hand
OpenSSF Scorecard every push to main, every Sunday at 03:47 UTC, by hand
Secret scan every pull request, every Friday at 04:55 UTC, by hand
Workflow security every push to main, every pull request that changes certain files, every Tuesday at 05:35 UTC, by hand
Bot health after 25 workflows, every day at 01:47 UTC, by hand
Clean-up every Sunday at 05:40 UTC, by hand
Dashboard every push to main that changes certain files, every 2 hours at :05, by hand
Labels every push to main that changes certain files, by hand
Settings every push to main that changes certain files, every Thursday at 06:05 UTC, by hand
Weekly report every Monday at 05:07 UTC, by hand

Checks

They check every change before it reaches main.

CI (python)

Python: the hash-pinned tools of requirements-dev.txt and the checks of scripts/check.sh, the same ones that run locally. The Python version comes from .python-version; the Newest Python workflow runs the checks with the newest one.

Diagrams

The diagram bot of main. The GitHub app, the stores and other renderers of Markdown show a ```mermaid block as code, so every diagram is a picture of its source in docs/diagrams/, and the check diagrams of each pull request keeps it that way. When main still has a ```mermaid block, a missing or stale picture, or a picture without its source, after the update of the blueprint that brought the pictures, a merge that went around the check or a change of the renderer, this workflow draws the diagrams with scripts/diagrams.sh --extract and keeps one pull request with the fix from the branch bot/diagrams, as the release app, which merges itself once every check passes. It runs after every change of main that touches a diagram or a page of Markdown, and every week; when main is fixed otherwise, it closes the pull request. Drawing runs without secrets; only the job of the pull request has the release app, and runs nothing that it drew; without the release app, the run warns and names the command that fixes main. A failed run opens an issue, which the next weekly run closes once the bot passes again.

  • When: every push to main that changes certain files, every Tuesday at 04:45 UTC, by hand
  • Environments: release
  • Secrets: RELEASE_AUTOMATION_PRIVATE_KEY
  • Source: .github/workflows/diagrams.yml

Docs

The documentation website. MkDocs with the Material theme builds it from mkdocs.yml and the pages in docs/, strictly, so that a link or an anchor that leads nowhere fails: in every pull request as the check docs, and on main, where the same job hands the website that it built to GitHub Pages, and a second one publishes it. A repository without mkdocs.yml or a home page in docs/ has no website, and the check passes, so that it can be required everywhere. Where another workflow publishes GitHub Pages, as the Dashboard of the blueprint does with its dashboard, a run on main only builds the website; in a fork, where the Dashboard doesn't run, Docs publishes it. A pull request keeps the website that it built as the artifact website for a week, which the report of the PR coach links. The tools are the hash-pinned ones of stacks/docs/requirements-docs.txt, which the blueprint keeps current, and the files that Material downloads for the website stay in a cache for a week. After the build, lychee checks every link and anchor of the pages that were built, blueprint.py site-check what the hooks of the website promise, and axe-core every page in the light and the dark scheme for what WCAG 2.2 A and AA ask, with the locked tools of .github/a11y/. The job that publishes the website signs its provenance and asks the published website for its pages.

  • When: every push to main, every pull request, by hand
  • Required checks: docs
  • Environments: github-pages
  • Source: .github/workflows/docs.yml

Downstream

The update of every repository that takes the blueprint, before a release brings it there. downstream.py clones each repository of tests/answers/, read-only, and updates it as its blueprint bot does, without --trust: once to the commit of main that the pull request merges into and once to the pull request. The summary names, for each repository, the files whose update the pull request changes and those that the update leaves a conflict in. A conflict that the pull request brings into a file that runs, such as a workflow or a script, fails the check, one in a page warns, and so do answers in tests/answers/ that differ from those of the repository. In the pull requests that change what the repositories get from the blueprint, and every week for the update to main, since the repositories change on their own.

No required check: a conflict can be the right change, with a line in the changelog that says how a repository resolves it. The Variants workflow makes a repository with the answers of each one and runs its checks.

The link bot. lychee checks the links of the documentation, and the anchors that they lead to, also in the README of another repository, on every change of a Markdown file or of a page of HTML in docs/, and every week, since sites move without a change here. A pull request checks the documents that it changes, so that a site that is down for a moment fails only the pull requests that link to it; one that changes the rules or moves or deletes a file checks every document, as main and the weekly run do. The answers of the sites stay in a cache for a day. A broken link fails the check of a pull request; the weekly run keeps one issue with the broken links open until they are fixed, and closes it then. lychee.toml holds the rules, .lycheeignore the exceptions of this repository. The entry in the changelog that most pull requests bring, in CHANGELOG.md or in a fragment of changelog.d/, starts no check on its own; the weekly run checks the links of the changelog.

  • When: every push to main that changes certain files, every pull request that changes certain files, every Monday at 05:50 UTC, by hand
  • Source: .github/workflows/links.yml

Lint

The checks every repository of the blueprint shares: the workflows, the license of every file (REUSE), the Markdown of every document with hints at its spelling and style, the pictures of its diagrams, the sign-off of every commit (DCO) and the set-up of the issue assistant. In every pull request, not after a merge: main takes a pull request only when it is up to date, so a merge gives main the files that the pull request passed with.

Mutation

The mutation tests. Every week they change the code in thousands of small ways (mutants) and run the tests against each: a mutant that no test notices shows a line that the tests run but don't check. mutmut does it for Python, Infection for the PHP of a Nextcloud app. The plan splits the Python into parts of about the same number of mutants, each a job of its own, so that a large project finishes within the limits of GitHub; the report puts the parts together into one score, lists first the survivors that change the logic, with their changes, and keeps the details as an artifact. A low score fails nothing, a run that measures nothing fails, with the tests that failed without any mutant; a score of the default branch that drops below that of the run before opens one issue, which closes once a run is back. A pull request that changes how the mutants run tries them and shows a defect as a red check. That check isn't required: a pull request that merges itself, such as an update of mutmut, doesn't wait for it.

Newest Python

The checks of scripts/check.sh with the newest Python, beta included, which warns early of what the next Python of the runners breaks: after every change of main and every week. Not in pull requests: a new Python breaks main and every pull request alike, and no pull request waits for this check. A workflow of its own, so that its warning leaves the CI of main green, whose coverage the report of the PR coach compares every pull request with. Since no pull request shows the warning, a failure keeps one issue open, which the weekly run closes once the checks pass again.

Next runner

The jobs of the blueprint, and of every repository made from it, run on a runner named with its version, ubuntu-24.04: when GitHub moves ubuntu-latest to a new image, one job at a time over weeks, none of them moves with it by surprise. This workflow runs the Variants on the next runner every week: the checks of every kind of project, a new one and one updated from the latest release, with their real tools. Once they pass there, one pull request moves every job to it. Not in pull requests and not a required check: a new image breaks main and every pull request alike, and none waits for it. A failure keeps one issue open, which the next run closes once the variants pass again. This workflow belongs to the blueprint alone.

Pull request title

Pull requests are squashed into one commit named after their title, and the release bot builds the version and the changelog from these names. So the title must be a Conventional Commit; its type also sets the label of the pull request. A pull request that changes something for users, a feat, fix or perf, also says so in a fragment of the changelog, a new file in changelog.d/, or under Unreleased in CHANGELOG.md, which becomes the notes of the release.

Variants

Renders every kind of project from this commit and runs what a new repository of that kind runs from its first commit: scripts/check.sh with the tools of its stack, the build of its website, the lint of its workflows, and hassfest or Hadolint where they apply. Every week as well, since the tools and Home Assistant move on their own.

The same again for a repository of each kind that starts from the latest release and that the blueprint bot brings to this commit with copier update: the update must give every file of the blueprint as a new repository has it, without a conflict and without --trust, and the files of the project, which it keeps, must still pass the checks of this commit.

And the same update with the answers of each repository that takes the blueprint, from tests/answers/ (adopted): without the sample code, with their licenses, their languages and their discussions. Such a repository brings its own code and tests, so these legs check what the blueprint gives it alone: the update, the website and the lint of the workflows and of the Markdown. The Downstream workflow updates the repositories themselves.

Not after a merge: main takes a pull request only when it is up to date, so a merge gives main the tree that the pull request ran here. Nor in the release pull request, which changes only the changelog and the version, files that no project gets. The check variants stands for every variant in the rules of main: it passes when each one passed, or when none had to run.

The variants run on the runner of the blueprint's jobs, ubuntu-24.04. The Next runner workflow runs them on the next one every week, before the jobs move to it, and so can a run started here by hand.

  • When: every pull request, every Wednesday at 05:10 UTC, by hand, when another workflow calls it
  • Required checks: variants
  • Source: .github/workflows/variants.yml

Windows

The checks of scripts/check.sh on Windows, in Git Bash, as a contributor on Windows runs them before a push; the other workflows run on Linux only. Every week, and for a pull request that changes the code, the tests, the template they render (whose paths must be valid on Windows too) or what they run with. Not a required check. This workflow belongs to the blueprint alone.

Releases

They make a signed release of what reaches main, deliver it and verify it as its users can.

---
config:
  flowchart:
    wrappingWidth: 400
---
flowchart TB
    accTitle: From a change to a release
    accDescr: A pull request whose title is fix, feat or feat! merges into main once every check passes. The release bot keeps a release pull request with the next version and the text of the fragments and of Unreleased. Merging it signs its tag first, in the next run of the release bot, then builds the release in isolation once the tag checks out, signs and publishes it, delivers it and verifies it, then and every week. With BETA_CHANNEL, every change for users also becomes a beta.

    pr["Pull request<br>fix: · feat: · feat!:"]
    main["main"]
    rpr["`Release pull request<br>chore: release x.y.z<br>notes from changelog.d/<br>and *Unreleased*`"]
    beta["Beta x.y.z-beta.N<br>for testers"]
    tag["Signed tag<br>at the merge, by the next run<br>checked before the build"]
    build["Isolated build<br>SLSA Build Level 3"]
    release["Release<br>package · SBOMs<br>provenance"]
    deliver["Delivery<br>PyPI · npm · GHCR<br>App Store · HACS"]
    verify["Verified as users can<br>at once and every week"]

    pr -- "every check passes" --> main
    main -- "release bot" --> rpr
    main -. "BETA_CHANNEL" .-> beta
    rpr -- "merged" --> tag --> build --> release
    release --> deliver
    release --> verify

    classDef bot fill:#526cfe2e,stroke:#526cfe,stroke-width:2px
    classDef done fill:#16a34a2e,stroke:#16a34a,stroke-width:2px
    class rpr bot
    class release done

Build the release

The build of a release, called by the release workflow only: it packages the release, writes the SBOMs of what it ships, the licenses of its third-party components and the advisories that don't affect it, attests the signed provenance of every asset and attaches all of it to the draft. As a reusable workflow of its own, it is isolated from the rest of the release workflow, and the provenance names it as the build, as SLSA Build Level 3 asks; verify-release.yml checks that.

Release

The release bot. release-please keeps a pull request for the next release, up to date with main. Its version follows from the Conventional Commit titles merged since the last release (fix: a patch, feat: a minor, ! or BREAKING CHANGE: a major version) and is decided anew with every merge. What the pull requests wrote in the fragments of changelog.d/ and under "## Unreleased" of CHANGELOG.md becomes the section of the release, and the fragments leave with it; without such text, the section lists the pull requests. Merging the pull request creates the release as a draft, on a tag that this workflow signs first, and the build checks the tag before anything else. The jobs below attach the package, its SBOMs (SPDX and CycloneDX), the licenses of its third-party components, the advisories that don't affect it (OpenVEX) and its signed provenance, write the notes, make the release public, announce it in the discussions, and then deliver it where the repository variable PUBLISH_TO says: pypi, npm, ghcr, hacs, nextcloud-appstore, github-action or none. Last, verify-release.yml checks the release as its users can. A release that only updates dependencies merges itself once its checks pass.

  • When: every push to main, every day at 05:25 UTC, by hand
  • Environments: npm, pypi, release
  • Secrets: APPSTORE_TOKEN, RELEASE_AUTOMATION_PRIVATE_KEY
  • Source: .github/workflows/release.yml

Rollout

The rollout of the blueprint. Once the Release workflow has published and verified a release, it starts the blueprint bot (Blueprint update) in every repository of the release app that has one, so that each repository gets the release within minutes instead of with the bot's weekly run. It goes from one repository to the next, the quickest checks first, and gives the bot and the checks of its update pull request up to 15 minutes before it starts the next one, so that the update pull requests don't all wait for the runners of the account at once. A repository that it can't start, or whose bot fails, fails the run by its name; the others get the release all the same. Started by hand, it rolls out the latest release. Its tokens of the release app can start workflows and read, nothing else, each in one repository. It runs in the blueprint only.

SBOM

A software bill of materials of the whole repository in SPDX, every week, with the tools of its checks, its website and its workflows, as an artifact of the run. A release carries the SBOMs of what it ships instead (build-release.yml), which its users' scanners read. Not for every change: no pull request reads it, and the dependency review checks what a pull request brings in.

Release verification

The release verification bot. It checks a release the way its users can: every asset carries the signed provenance of this repository's release workflow, both in GitHub and in the attached bundle; the SBOMs are attached and describe what the release ships, by its name and version; the release is immutable and its commit signed; and where the repository variable PUBLISH_TO delivers it, the same release is there. The release workflow runs it after every release, and it checks the latest release every week. A failed check fails the run, which GitHub reports to the maintainer.

Bots

They answer issues and pull requests, fix what a command can fix and keep the pull requests moving.

After the checks

The bots that act on a finished run of the checks and of the release. One workflow for all, so that every run starts one run after it, not one for each bot that mostly has nothing to do. None checks out or runs the code of a pull request.

The flaky-test bot: when a run of the tests fails for the first time, or a job of it runs out of time, it runs the failed jobs once more. A job that passes then failed without a change of the code: its test is flaky, and the bot reports it in one issue, with the step, the test and the error that failed, so that it gets fixed instead of failing pull requests at random. A job that fails again is a real failure and stays red, and so does one that failed the same way for the commit before: running it again would only keep a runner busy. A pull request names its jobs and steps and writes its logs, so the issue shows them only as code, and the branch of a fork not at all.

The bot for cancelled checks: a newer run of a workflow cancels the one still running for the same pull request. GitHub doesn't always create the runs in the order of the commits, so a late run for an older commit can cancel the run for the head of the pull request; its required checks then stay cancelled and block the merge. When a run for the head of an open pull request was cancelled that way and no newer run of its workflow for the same commit exists, the bot starts it again. A run that a person cancelled, or one whose commit a newer push replaced, stays cancelled. The quick checks avoid the race: their runs for different commits never cancel each other.

The bot for failed releases: when the release that a merge started fails, such as at a push of its tag that GitHub refused for a moment, it runs the failed jobs once more, where they stopped, instead of leaving the release and its rollout to a person. The release workflow takes up a tag that it pushed already.

The PR coach: after every run of the checks of a pull request, one comment on it explains each check that failed: what it found, why that matters and how to fix it, with the commands to run, from the explanations of .github/checks.toml and .github/checks.project.toml. It works for a pull request from a fork as well, whose own runs can't write to it. The comment follows every push and turns into a short all-clear once every check passes. A check that was cancelled, as a job that ran out of time is, hasn't passed either. It reads the check runs and their annotations from the API and shows their text only as code, and waits up to three minutes for the checks of other apps, such as the alerts of code scanning, which end after the last workflow. The scanners, which run in the pull requests that change what they read, start it as well. On a pull request of a person, the comment also says what the pull request makes: the next version, from its title and the release-please files of main, and the entry that it adds to the changelog; the coverage of its tests against that of main, file by file, from the coverage reports that the checks keep as the artifact "coverage", which a pull request from a fork writes as it likes; and the website that the check docs built, with the pages that it changes. A pull request whose checks never failed gets the comment only when it has something of this to say, and one of a bot never.

The fix bot: a check whose findings a command fixes, such as the format of the code, the whitespace of a text file or the rules of markdownlint, runs the commands of autofix in .github/checks.toml when it fails, on the head of the pull request or on main, and keeps the files that they changed as an artifact fixes-*. Once the checks of the commit that fix have ended, the bot takes these files as data: on a pull request from a branch of this repository, also one of Renovate or another bot, it commits them as one commit onto the branch, as the release app, so that the checks run again; on one from a fork, which no bot may push to, it suggests them at their lines, at most ten in one review, never one twice; on main, it keeps one pull request from bot/self-heal with them, which merges itself, and closes it once main needs no fix. blueprint.py of main lets through only text files that the commit has already, outside .git and the workflows. It commits at most twice in a row onto its own commits, and leaves a pull request with the label no-bot-fixes alone. When the bot fails, it says so on the pull request; on main, it keeps one issue open until it passes again.

The diagram bot: when the check diagrams of Lint finds a ```mermaid block, which the GitHub app shows as code, or pictures of diagrams that differ from their sources in docs/diagrams/, in a pull request from a branch of this repository, it commits what scripts/diagrams.sh --extract changed in the check onto that branch, as the release app, so that the checks run again, and says in a comment which blocks became pictures. blueprint.py of main takes only sources, SVG pictures and pages of Markdown from the artifact "diagrams". When the bot can't commit, it says so on the pull request, with the command. A pull request from a fork keeps the failing check, with the command that fixes it.

Area labels

The labeler bot. It gives a pull request the labels of the areas whose files it changes, by the paths in .github/labeler.yml, and takes them away when it no longer changes them. The type label comes from the title check.

Dependabot maintenance

Dependabot keeps only the Features of the dev container (dependabot.yml); Renovate merges its own updates, those of actions pinned to a commit of a branch included. Routine updates merge on their own once every required check passes; a major update waits for the maintainer. Updates of runtime dependencies are committed as fix(deps) and so lead to a patch release (see release.yml). Auto-merge is turned on as the release app, as Renovate's is: a merge that auto-merge does with the token of a workflow starts no workflow, so the release bot and the branch bot would wait for their schedules, which GitHub runs as it can, often hours late. The merge of the app starts them at once.

Docs pictures

The picture bot of the blueprint's own documentation. scripts/docs-pictures.sh records a copy of the latest release with Copier and photographs the dashboard of the website, so the bot runs after a release that changed what they show: the questions or the output of Copier, the files and workflows of a new repository, the image of the recording or the dashboard, as .github/docs-pictures/paths.txt lists them, and on demand. It renders the pictures without secrets, keeps those whose pixels stayed the same (blueprint.py screenshots keep), and keeps one pull request from the branch bot/pictures with the others, as the release app, which merges itself once every check passes. The recording waits for the network, so a frame that repeats the one before counts once and the time each frame shows not at all. A failed run keeps an issue of the bot-health bot open until a run passes again. It runs in the blueprint only; every repository has the Screenshots workflow.

Issue assistant

A first analysis of new issues, the labels of the issue forms and the answers that the assistant or the maintainer waits for: https://github.com/Dennis-Otto/issue-assistant

event no AI: labels, the /assistant commands of a comment and the next task analyze the AI engine of .github/issue-assistant/config.toml reads the checkout and a context folder and answers with JSON; read-only token, read-only tools, egress-firewall runner (.github/egress-firewall.yaml) apply no AI: checks the engine's answer and is the only job that writes

event and apply block every host but GitHub's with Harden-Runner.

Without the engine's secret in the environment issue-assistant, or with the repository variable ISSUE_ASSISTANT_AI set to "off", only labels and the lifecycle work. The issue assistant's check command keeps this file equal to its template.

Issue lifecycle

Without AI: reminds reporters who were asked for information after 15 days, closes their issue after 30 days without an answer, closes a possible duplicate 3 days after the notice unless someone objected, labels the issues that a merged pull request fixes, and closes them with a link once a release ships the fix. Merges and releases start it at once, and every morning catches what was missed. A fixed issue that a release closes is filed under the release's milestone. The job blocks every host but GitHub's with Harden-Runner. https://github.com/Dennis-Otto/issue-assistant

Renovate

The dependency bot of every repository of the blueprint. On its schedule, which GitHub runs as it can, a few times a day and often hours late, and on demand, it runs Renovate for each repository of the owner that has a .github/renovate.json5, as the release app: it opens the updates of the actions, the dependencies, the images and the pins, and the checks of each repository decide whether they merge. The rules of the updates are in the .github/renovate.json5 of each repository; .github/renovate-global.json5 holds those of the bot itself. It runs in the blueprint only. A pull request that changes the rules or a lock file shows whether Renovate accepts the rules and still reads every file of the dependencies.

  • When: every 2 hours at :17, every push to main that changes certain files, every pull request that changes certain files, by hand
  • Environments: release
  • Secrets: RELEASE_AUTOMATION_PRIVATE_KEY
  • Source: .github/workflows/renovate.yml

Screenshots

The screenshot bot. A repository turns it on with scripts/screenshots.sh, which renders its pictures in place, wherever the documentation, the stores or the social preview take them from, and lists in .github/screenshots-paths.txt the files whose change can change them, as the paths of a workflow; without the script, every run ends after its plan. After a change of main that touches a file of the list, after every release, whose version a picture may show, every week and on demand, it runs the script without secrets, with Docker and the tools of the website in .venv-docs as CONTRIBUTING.md sets them up, and compares each picture that changed with the one of main by its pixels: a picture whose pixels stayed the same keeps its file, so that a new encoder, browser or runner changes nothing. When pixels changed, it keeps one pull request from the branch bot/screenshots, as the release app, which merges itself once every check passes, and closes it when main looks as rendered without it. The bot commits only pictures, by plain paths, outside .git and the workflows; without the release app, the run warns and keeps the pictures as an artifact. A failed run keeps an issue of the bot-health bot open until a run passes again.

Issue assistant sync

The sync bot of the issue assistant, in the blueprint alone. The four workflows of the issue assistant and its firewall policy are templates of its release, with which the check issue-assistant compares every repository; the blueprint keeps the copies in .github/ that every repository made from it renders. Once a day, and on demand, this workflow takes the latest published release of the issue assistant and, once it has verified that the release workflow of the issue assistant made it, runs install of that release in the root of the blueprint, takes over its firewall policy and moves every pin of the action to it, that of the check in the Lint workflow too, with sync_issue_assistant.py. It keeps one pull request with the result from the branch bot/issue-assistant, as the release app, which merges itself once every check passes; the next release of the blueprint brings it to every repository. A release that it can't verify changes nothing and fails the run. Installing runs without secrets; only the job of the pull request has the release app, and it runs nothing of the release. A pull request that changes the bot runs it without a pull request of its own, as a start by hand with dry_run does. The bot-health bot keeps one issue open about a failed run, with the reason that it gave, which the next run that passes closes.

Update branches

The branch bot. main only takes pull requests that are up to date with it, and auto-merge doesn't update a branch by itself. So only one pull request can merge next, and each merge leaves the others behind again: bringing all of them up to date after every merge ran the checks of every waiting pull request for the one that merged. The bot keeps a line instead, the pull requests that wait for auto-merge in the order in which it was turned on, and brings only the first one up to date that can merge, as the release app, so that its checks run again and it merges once they pass. While its checks run, the others wait; its merge, or a required check of it that fails, starts the next run, and turning auto-merge on starts one at once. A pull request whose required check failed waits for a fix and leaves its place to the next one. One that conflicts with main gets the label merge-conflict and one comment. The label goes once it no longer conflicts; the comment stays for the history, so a later conflict brings back only the label. A request that GitHub refuses is a warning about that pull request, and the bot goes on with the next one. The release bot keeps its own pull request current. Renovate's pull requests take their place in the line as well, which spares them the wait for its next run: Renovate commits as the release app too, so it still takes the branch for its own; one that conflicts with main it builds anew itself. The bot checks out main alone, for blueprint.py, which holds the logic, and runs nothing of a pull request.

Welcome

The welcome bot: a person's first pull request in this repository gets a comment with what helps it along. New issues get the issue assistant's analysis instead.

Security

They look for vulnerabilities, leaked secrets and risky dependencies.

CodeQL

The code analysis of GitHub. CodeQL reads the code of every language of this repository and its workflows with the queries of security-extended, for every pull request, every push to main and every week, and sends what it finds to code scanning, where a new alert of a pull request stops its merge.

  • When: every push to main, every pull request, every Wednesday at 04:17 UTC, by hand
  • Required checks: codeql
  • Source: .github/workflows/codeql.yml

Dependency review

The review of the dependencies that a pull request adds or changes: one with a known vulnerability of moderate severity or more, or with a license outside the list of .github/dependency-review.yml, fails it. An advisory that the repository accepts goes into allow-ghsas there, with its reason. Started by hand, it compares two commits.

Findings

Without AI, every morning, after each scan of code scanning on main and when the accepted findings change: dismisses the code scanning alerts that .github/findings.toml accepts, with its reason, and fails while any other alert is open. The run names an open alert by its number and link only, which only maintainers can follow; nothing becomes a public issue. The job blocks every host but GitHub's with Harden-Runner. https://github.com/Dennis-Otto/issue-assistant

Fuzzing

Coverage-guided fuzzing with Atheris of every target in fuzz/: a minute each in every pull request that changes the code, the targets or the tools, and every week 25 minutes shared by all targets, so the job ends within its timeout however many targets there are. Not after a merge, which gives main the tree that its pull request fuzzed. A finding fails the run, with the input that caused it in the log.

OSV-Scanner

Every lock file of the repository against the OSV database of known vulnerabilities: in every pull request that changes a lock file the vulnerabilities that it brings in, and on main and every week all of them. Findings go to code scanning, where the Findings workflow keeps them fixed or accepted; osv-scanner.toml lists those that the project accepts, each with its reason, such as an advisory in a test base that pins its dependencies.

OpenSSF Scorecard

The OpenSSF Scorecard: it rates the security practices of the repository, such as its branch protection, its pinned actions and its signed releases, after every push to main and every week, publishes the score for the badge of the README and sends each finding to code scanning.

Secret scan

Gitleaks looks for secrets in the commits of every pull request, and every week in the whole history of main. Not after a merge, which brings only the commits of a pull request that it scanned; GitHub's secret scanning with push protection guards every push as well.

Workflow security

zizmor checks every workflow for the ways a workflow gets attacked: template injection, permissions wider than needed, credentials that persist, unpinned actions and dangerous triggers: in every pull request that changes a workflow, an action or the settings of Dependabot, and on main and every week for the analysis that code scanning compares pull requests with. Its findings go to code scanning, where the Findings workflow keeps them fixed or accepted; .github/zizmor.yml holds what the repository accepts on purpose.

  • When: every push to main, every pull request that changes certain files, every Tuesday at 05:35 UTC, by hand
  • Source: .github/workflows/zizmor.yml

Maintenance

They keep the repository, its settings, its labels and its reports current.

Bot health

The bot-health bot. GitHub mails the failure of a scheduled run only to whoever last changed its schedule, and that of another run to whoever started it, which here is mostly the release app, so a failing bot went unnoticed for days. When a run of a bot or a scheduled workflow ends on the default branch, this bot runs its failed jobs once more if a second try can pass; a failure that stays keeps one issue per workflow open, with the label bot-failure, the failed jobs, their steps and their first errors, and the next run that passes closes it. Every night it also looks at the latest run of every workflow, those of the project and the checks of main too, in case GitHub lost an event, and starts a workflow whose schedule GitHub missed or switched off. Runs of pull requests start nothing here. It checks out the default branch only, for blueprint.py, never the code of a pull request; blueprint.py bot-health holds the logic.

Clean-up

The clean-up bot. Every week it deletes what nobody needs any more: the branches that main holds completely and that no open pull request uses, and the caches of the Actions that belong to closed pull requests. A branch with commits that main doesn't have stays; the bot lists it once it is a month old, so that its owner decides. It never touches main, release, Renovate or Dependabot branches.

Dashboard

The website of the blueprint on GitHub Pages: its documentation, which MkDocs builds from mkdocs.yml and docs/ as in every repository, and under dashboard/ the dashboard of the owner's public repositories: the release of the blueprint each one is on, its releases, its open pull requests, the last runs of its main workflows and its OpenSSF Scorecard (dashboard.py). On a schedule, which GitHub keeps as it can, and after every change of either, of the changelog that a page includes and of the code that reads GitHub. A history of the published dashboard that can't be read fails the build, so that the website keeps its trends until the next run instead of a history of one day. Everything it shows is public anyway, so the job's own token reads it. This workflow belongs to the blueprint alone; the Docs workflow checks the documentation in every pull request and publishes the website of every other repository.

Labels

Creates and updates the repository's labels from .github/labels.toml. It never deletes a label; one that is missing from the file is reported in the summary. The job blocks every host but GitHub's with Harden-Runner. https://github.com/Dennis-Otto/issue-assistant

Settings

The settings bot. Every week, and after each change of the settings as code or of its own code, it brings the settings of the repository on GitHub in line with .github/repository.toml, .github/repository.project.toml and .github/labels.toml, as the release app, which may write the administration of the repository. What the API can't set, such as the value of a secret, and what GitHub refused fail the run and show in its summary, with the command that sets it or the permission that the app lacks.

  • When: every push to main that changes certain files, every Thursday at 06:05 UTC, by hand
  • Environments: release
  • Secrets: RELEASE_AUTOMATION_PRIVATE_KEY
  • Source: .github/workflows/settings.yml

Weekly report

The weekly report of the owner's public repositories: every Monday it posts what they released, merged and fixed in the week (weekly.py) in the discussions of the blueprint. Everything it reports is public anyway, so the job's own token reads it. This workflow belongs to the blueprint alone.