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
25 changes: 0 additions & 25 deletions .github/release-health.json

This file was deleted.

135 changes: 56 additions & 79 deletions .github/workflows/release-health.yml
Original file line number Diff line number Diff line change
@@ -1,133 +1,110 @@
name: Release health

# Nothing here publishes anything. Releases stay `workflow_dispatch`-only by
# deliberate design (see release.yml: "Publication is a deliberate operation
# from protected main"), and that gate exists because Flow 1.13.0/1.14.0 were
# yanked for shipping AGPL benchmark files. This workflow only LOOKS.
#
# Why it exists: PR #51 removed a hosted-recognizer path that uploaded raw
# audio waveforms to a third party, merged to main, and sat unreleased while
# PyPI's only installable version (1.2.0) still contained the upload path --
# with two downstream packages resolving `openadapt-capture>=1.1.0` unpinned.
# Nothing noticed. It shipped as 1.2.1 only because a human happened to look.
#
# Three triggers, one script:
# schedule -- the actual detector. Bounds "how long can a releasable fix
# sit unnoticed" to ~7h instead of "until someone looks".
# workflow_run -- fires when a release run COMPLETES NON-SUCCESSFULLY, which
# includes `cancelled` and `skipped`. `if: failure()` steps
# inside a release workflow cannot see either, and a run
# cancelled at a human approval gate is exactly that case.
# pull_request -- runs the detector's own offline scenarios when the
# detector changes. A detector nobody has seen fail is a
# detector nobody should trust.

# Visibility only: publishing remains a deliberate release.yml dispatch from
# protected main. This check reads Capture release state and maintains one issue.
on:
schedule:
# Every 3 hours. One stdlib step, no dependency install, no cache, so this
# costs seconds a day rather than a matrix. With the 4h grace window below,
# a releasable commit is surfaced within ~7h at worst.
- cron: '23 */3 * * *'
# Two lightweight checks per day, plus immediate non-success release events.
- cron: '23 */12 * * *'
workflow_dispatch:
workflow_run:
workflows: ["Release and PyPI Publish"]
types: [completed]
pull_request:
paths:
- 'scripts/check_release_health.py'
- '.github/release-health.json'
- '.github/workflows/release-health.yml'

concurrency:
# Keyed by ref so a pull request's self-test can never queue behind (or be
# cancelled by) a scheduled main run. The check is stateless and idempotent,
# so superseding an in-flight one costs nothing.
group: release-health-${{ github.ref }}
# PR self-tests must not cancel (or be cancelled by) the singleton live check.
group: capture-release-health-${{ github.event_name == 'pull_request' && github.event.pull_request.number || 'main' }}
cancel-in-progress: true

permissions:
contents: read

jobs:
self-test:
name: Prove the detectors fire and stay quiet
name: Prove the release-gap decisions
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# 3.12 explicitly: openadapt-desktop release run 29514474536 died on
# `ModuleNotFoundError: No module named 'tomllib'` on a pre-3.11 runner.
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- run: python scripts/check_release_health.py --self-test
- run: python3 scripts/check_release_health.py --self-test

check:
name: Detect unreleased work and silently skipped publishes
name: Check Capture release state
if: >-
github.event_name != 'pull_request' &&
(github.event_name != 'workflow_run' ||
github.event.workflow_run.conclusion != 'success')
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
issues: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"

- name: Evaluate every release lane
id: evaluate
- name: Evaluate release state
id: health
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
python scripts/check_release_health.py \
--markdown release-health.md \
--github-output "${GITHUB_OUTPUT}" \
--run-url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
run: >-
python3 scripts/check_release_health.py
--markdown release-health.md
--github-output "${GITHUB_OUTPUT}"

# One issue per repository, rewritten in place. A new issue every day is
# the same as no issue: it stops being read. Editing a body does not
# notify, so a persistent gap does not become a daily ping either.
- name: Open or update the single release-health issue
if: steps.evaluate.outputs.alert == 'true'
- name: Find the single release-health issue
id: issue
if: steps.health.outputs.evaluated == 'true'
env:
GH_TOKEN: ${{ github.token }}
TITLE: "Release health - unreleased work or an incomplete publish"
shell: bash
run: |
set -euo pipefail
# Match on a colon-free prefix and compare the full title in jq: a
# colon inside a GitHub search phrase is parsed as a qualifier.
EXISTING=$(gh issue list --repo "${GITHUB_REPOSITORY}" --state open \
--search "in:title \"Release health\"" --json number,title \
--jq '[.[] | select(.title == env.TITLE)][0].number // empty')
if [ -n "${EXISTING}" ]; then
gh issue edit "${EXISTING}" --repo "${GITHUB_REPOSITORY}" \
--body-file release-health.md
echo "Updated issue #${EXISTING}."
else
gh issue create --repo "${GITHUB_REPOSITORY}" \
--title "${TITLE}" --body-file release-health.md
fi
record="$(gh api --paginate --slurp --method GET \
"repos/${GITHUB_REPOSITORY}/issues" -f state=all -f per_page=100 |
jq -c --arg title "${TITLE}" \
'[.[][] | select(has("pull_request") | not) | select(.title == $title)]
| sort_by(.number) | last // {}')"
{
echo "number=$(jq -r '.number // empty' <<<"${record}")"
echo "state=$(jq -r '.state // empty' <<<"${record}")"
} >> "${GITHUB_OUTPUT}"

- name: Close the issue once every gap is closed
if: steps.evaluate.outputs.alert != 'true'
- name: Open, reopen, or update the release-health issue
if: steps.health.outputs.alert == 'true'
env:
GH_TOKEN: ${{ github.token }}
TITLE: "Release health - unreleased work or an incomplete publish"
ISSUE: ${{ steps.issue.outputs.number }}
shell: bash
run: |
set -euo pipefail
# Match on a colon-free prefix and compare the full title in jq: a
# colon inside a GitHub search phrase is parsed as a qualifier.
EXISTING=$(gh issue list --repo "${GITHUB_REPOSITORY}" --state open \
--search "in:title \"Release health\"" --json number,title \
--jq '[.[] | select(.title == env.TITLE)][0].number // empty')
if [ -n "${EXISTING}" ]; then
gh issue close "${EXISTING}" --repo "${GITHUB_REPOSITORY}" \
--comment "Every release lane is published and current as of ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}. Closing automatically."
if [ -n "${ISSUE}" ]; then
jq -n --rawfile body release-health.md '{body: $body, state: "open"}' |
gh api --method PATCH "repos/${GITHUB_REPOSITORY}/issues/${ISSUE}" \
--input - >/dev/null
else
echo "No release-health alerts and no open issue."
jq -n --arg title "${TITLE}" --rawfile body release-health.md \
'{title: $title, body: $body}' |
gh api --method POST "repos/${GITHUB_REPOSITORY}/issues" \
--input - >/dev/null
fi

- name: Close the issue when the gap is resolved
if: >-
steps.health.outputs.close == 'true' &&
steps.issue.outputs.state == 'open'
env:
GH_TOKEN: ${{ github.token }}
ISSUE: ${{ steps.issue.outputs.number }}
shell: bash
run: |
set -euo pipefail
jq -n --rawfile body release-health.md \
'{body: $body, state: "closed", state_reason: "completed"}' |
gh api --method PATCH "repos/${GITHUB_REPOSITORY}/issues/${ISSUE}" \
--input - >/dev/null
Loading