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
28 changes: 28 additions & 0 deletions .github/AGENTS.d/readme-badges.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
paths:
- .github/workflows/readme-badges.yml
invariant: Render job on master only; both checkouts at github.sha; publish job contents: write only; no workflow_run trigger.
---
# README badges workflow (ADR-3519)

`.github/workflows/readme-badges.yml` renders + publishes README status
badges onto `badges` branch. Script rules, design check, carry:
[scripts/ci/AGENTS.d/readme-badges.md](../../scripts/ci/AGENTS.d/readme-badges.md).
Human guide: `docs/development/readme-badges.md`.

- Guard: render job `if:` = policy repository + `https://github.com` +
`github.ref == 'refs/heads/master'`. Publish job `needs: render`, so
skipped render skips publish. Manual run on other branch -> nothing runs;
publish token never meets branch copy of workflow. Names come from
`.github/repository-security-policy.json`; `test_readme_badges.py` keeps
them equal.
- Checkout: both jobs `ref: ${{ github.sha }}`, `persist-credentials: false`.
Never `ref: master`: master push between jobs hands publisher other code
than renderer ran.
- Rights: top level `permissions: {}`; render job `actions: read` +
`contents: read`; publish job `contents: write` only. No other write scope.
- Trigger: `schedule` (minutes 7, 22, 37, 52) + `workflow_dispatch`. No
`workflow_run` (zizmor dangerous trigger; fork branch named `master`
matches `branches` filter).
- Ref: publish writes `refs/heads/badges` only, `force: false`;
GitHub-signed commit required.
1 change: 1 addition & 0 deletions .github/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ broken `.c` file).
| `workflows/libvmaf-build-matrix.yml`, `/scripts/ci/run_meson_test.py`, `/core/test/test_meson_secret_env_sanitization.py` | [meson-sanitization](AGENTS.d/meson-sanitization.md) | Every workflow step running Meson/Ninja test invokes run_meson_test.py; resolve Python before sudo. |
| `workflows/lint-and-format.yml`, `/scripts/sync-pelorus-interop.sh` | [pelorus-mirror](AGENTS.d/pelorus-mirror.md) | Pre-Commit job verifies exact declared commit of VMAFx/pelorus before pre-commit; no moving branch or tag. |
| `/pyproject.toml`, `/renovate.json` | [python-requirement](AGENTS.d/python-requirement.md) | Root pyproject.toml keeps requires-python >=3.14 without patch component; renovate excludes root requires-python. |
| `workflows/readme-badges.yml` | [readme-badges](AGENTS.d/readme-badges.md) | Render job on master only; both checkouts at github.sha; publish job contents: write only; no workflow_run trigger. |
| `workflows/release-please.yml`, `workflows/supply-chain.yml`, `/scripts/release/verify-release-version.sh`, `/dev/Containerfile` | [release-fan-out](AGENTS.d/release-fan-out.md) | Single root package draft release; release-publish environment; native release-build stage; tag-bound recovery. |
| `/renovate.json`, `/scripts/ci/tests/test_renovate_file_patterns.py`, `/docs/development/dependency-bot.md` | [renovate-bot](AGENTS.d/renovate-bot.md) | Mend Renovate config in renovate.json; dependabot.yml disabled; RENOVATE_TOKEN repository secret. |
| `workflows/required-aggregator.yml`, `/scripts/ci/check-aggregator-names.sh`, `/scripts/ci/plan-ci-impact.py` | [required-aggregator](AGENTS.d/required-aggregator.md) | Aggregator runs on drafts; gates route in-job without paths filters; display names <=30 chars and match aggregator. |
Expand Down
122 changes: 122 additions & 0 deletions .github/workflows/readme-badges.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
name: README Badges
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Resolved

# Render the README status badges in one style (docs/development/readme-badges.md).
# scripts/ci/readme_badges.py reads the latest decisive push run of every badged
# workflow on master plus the OpenSSF Scorecard, OpenSSF Best Practices and
# Codacy public APIs; tools/badges/render.mjs draws the SVGs with the pinned
# shields.io renderer; the publish job commits them to the badges branch the
# README links. A source that fails keeps its last value for six hours, then
# reads "stale", and this run turns red so the failure is seen.

on:
# Every 15 minutes: a badge lags a master run by at most that. A
# workflow_run trigger would be faster, but zizmor rates it a dangerous
# trigger, and its branch filter would also match a fork's branch named
# master.
schedule:
- cron: "7,22,37,52 * * * *"
workflow_dispatch:
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Resolved

permissions: {}

concurrency:
group: readme-badges
cancel-in-progress: false

jobs:
render:
name: Read the sources and render
# github.com only: the job reads and the publish job writes through the
# GitHub API, which a mirror's token cannot reach (as in supply-chain.yml).
# Default branch only: a manual run picked on another branch runs nothing,
# so the publish token never meets a branch's own copy of this file.
# .github/repository-security-policy.json holds both names; a test keeps
# these in step with it.
if: >-
github.repository == 'VMAFx/vmafx'
&& github.server_url == 'https://github.com'
&& github.ref == 'refs/heads/master'
runs-on: ubuntu-26.04
timeout-minutes: 10
permissions:
actions: read
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# The commit this run started from, in both jobs: a master push
# between them cannot hand the publisher other code than the
# renderer ran.
ref: ${{ github.sha }}
persist-credentials: false
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: "3.14.10"
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "26.12.1"
- name: Install the pinned badge renderer
working-directory: tools/badges
run: npm ci --ignore-scripts --no-audit --no-fund
- name: Read the badge sources
env:
GITHUB_TOKEN: ${{ github.token }}
run: >-
python3 scripts/ci/readme_badges.py spec
--spec build/badges-spec.json --state build/badges/state.json
- name: Render the SVGs
run: node tools/badges/render.mjs build/badges-spec.json build/badges
- if: ${{ env.GITEA_ACTIONS != 'true' }}
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: readme-badges
path: build/badges/
retention-days: 1
if-no-files-found: error
- name: Upload artifact (Gitea)
if: ${{ env.GITEA_ACTIONS == 'true' }}
uses: actions/upload-artifact@c6a366c94c3e0affe28c06c8df20a878f24da3cf # v3.2.2
with:
name: readme-badges
path: build/badges/
retention-days: 1
if-no-files-found: error

publish:
name: Publish to the badges branch
needs: render
runs-on: ubuntu-26.04
timeout-minutes: 5
# contents: write is the narrowest token right that can create a commit;
# the job writes refs/heads/badges only, and the master ruleset refuses
# this token on master.
permissions:
contents: write
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Resolved
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# The commit this run started from, in both jobs: a master push
# between them cannot hand the publisher other code than the
# renderer ran.
ref: ${{ github.sha }}
persist-credentials: false
- if: ${{ env.GITEA_ACTIONS != 'true' }}
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: readme-badges
path: build/badges
- name: Download artifact (Gitea)
if: ${{ env.GITEA_ACTIONS == 'true' }}
uses: actions/download-artifact@a9bc5e6ef2cb54c177f32aa5726adaa15e7e2d59 # v3.1.0
with:
name: readme-badges
path: build/badges
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: "3.14.10"
- name: Commit the badges
env:
GITHUB_TOKEN: ${{ github.token }}
run: python3 scripts/ci/readme_badges.py publish --dir build/badges
- name: Fail the run when a source failed
run: python3 scripts/ci/readme_badges.py verdict --state build/badges/state.json
16 changes: 16 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,22 @@ repos:
files: '^(scripts/ci/(check-workflow-size\.py|tests/test_check_workflow_size\.py)|\.pre-commit-config\.yaml)$'
pass_filenames: false

- id: readme-badge-design
name: Every README image keeps the one badge design (ADR-3519)
entry: python3 -B scripts/ci/readme_badges.py check-readme --readme README.md
language: system
files: '^(README\.md|scripts/ci/readme_badges\.py)$'
pass_filenames: false
stages: [pre-commit, pre-push]

- id: test-readme-badges
name: README badge renderer and design check keep positive, negative and boundary cases (ADR-3519)
entry: bash -c 'python3 -B scripts/ci/tests/test_readme_badges.py && python3 -B scripts/ci/tests/test_ci_tier.py'
language: system
files: '^(README\.md|scripts/ci/(readme_badges|ci_tier|tests/test_readme_badges|tests/test_ci_tier)\.py|tools/badges/.*|\.github/workflows/.*\.yml|\.pre-commit-config\.yaml)$'
pass_filenames: false
stages: [pre-commit, pre-push]

- id: check-hosted-runner-labels
name: Workflow jobs name an explicit hosted Ubuntu image, an older one only with a dated fallback
entry: python3 scripts/ci/check_hosted_runner_labels.py
Expand Down
40 changes: 20 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@
<!-- 1 release/version -->
[![Release](https://img.shields.io/static/v1?label=release&message=v1.0.0-rc.3&style=for-the-badge&color=3b82f6&labelColor=111a35)](https://github.com/VMAFx/vmafx/releases) <!-- x-release-please-version -->
<!-- 2 CI/build -->
[![Tests](https://github.com/VMAFx/vmafx/actions/workflows/tests-and-quality-gates.yml/badge.svg?branch=master&event=push)](https://github.com/VMAFx/vmafx/actions/workflows/tests-and-quality-gates.yml)
[![Builds](https://github.com/VMAFx/vmafx/actions/workflows/libvmaf-build-matrix.yml/badge.svg?branch=master&event=push)](https://github.com/VMAFx/vmafx/actions/workflows/libvmaf-build-matrix.yml)
[![Lint](https://github.com/VMAFx/vmafx/actions/workflows/lint-and-format.yml/badge.svg?branch=master&event=push)](https://github.com/VMAFx/vmafx/actions/workflows/lint-and-format.yml)
[![FFmpeg](https://github.com/VMAFx/vmafx/actions/workflows/ffmpeg-integration.yml/badge.svg?branch=master&event=push)](https://github.com/VMAFx/vmafx/actions/workflows/ffmpeg-integration.yml)
[![Go](https://github.com/VMAFx/vmafx/actions/workflows/go-ci.yml/badge.svg?branch=master&event=push)](https://github.com/VMAFx/vmafx/actions/workflows/go-ci.yml)
[![Rust](https://github.com/VMAFx/vmafx/actions/workflows/rust-ci.yml/badge.svg?branch=master&event=push)](https://github.com/VMAFx/vmafx/actions/workflows/rust-ci.yml)
[![Tests](https://raw.githubusercontent.com/VMAFx/vmafx/badges/tests.svg)](https://github.com/VMAFx/vmafx/actions/workflows/tests-and-quality-gates.yml)
[![Builds](https://raw.githubusercontent.com/VMAFx/vmafx/badges/builds.svg)](https://github.com/VMAFx/vmafx/actions/workflows/libvmaf-build-matrix.yml)
[![Lint](https://raw.githubusercontent.com/VMAFx/vmafx/badges/lint.svg)](https://github.com/VMAFx/vmafx/actions/workflows/lint-and-format.yml)
[![FFmpeg](https://raw.githubusercontent.com/VMAFx/vmafx/badges/ffmpeg.svg)](https://github.com/VMAFx/vmafx/actions/workflows/ffmpeg-integration.yml)
[![Go](https://raw.githubusercontent.com/VMAFx/vmafx/badges/go.svg)](https://github.com/VMAFx/vmafx/actions/workflows/go-ci.yml)
[![Rust](https://raw.githubusercontent.com/VMAFx/vmafx/badges/rust.svg)](https://github.com/VMAFx/vmafx/actions/workflows/rust-ci.yml)
<!-- 3 security/scorecard -->
[![Security](https://github.com/VMAFx/vmafx/actions/workflows/security-scans.yml/badge.svg?branch=master&event=push)](https://github.com/VMAFx/vmafx/actions/workflows/security-scans.yml)
[![OpenSSF Scorecard](https://img.shields.io/badge/OpenSSF-Scorecard-06b6d4?style=for-the-badge&labelColor=111a35)](https://scorecard.dev/viewer/?uri=github.com/VMAFx/vmafx)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/14549/badge)](https://www.bestpractices.dev/projects/14549)
[![Codacy](https://app.codacy.com/project/badge/Grade/ca74eb98b9b94cfc8d60d965b836b55d)](https://app.codacy.com/gh/VMAFx/vmafx/dashboard)
[![Security](https://raw.githubusercontent.com/VMAFx/vmafx/badges/security.svg)](https://github.com/VMAFx/vmafx/actions/workflows/security-scans.yml)
[![OpenSSF Scorecard](https://raw.githubusercontent.com/VMAFx/vmafx/badges/scorecard.svg)](https://scorecard.dev/viewer/?uri=github.com/VMAFx/vmafx)
[![OpenSSF Best Practices](https://raw.githubusercontent.com/VMAFx/vmafx/badges/best-practices.svg)](https://www.bestpractices.dev/projects/14549)
[![Codacy](https://raw.githubusercontent.com/VMAFx/vmafx/badges/codacy.svg)](https://app.codacy.com/gh/VMAFx/vmafx/dashboard)
[![CodeRabbit](https://img.shields.io/badge/Reviews-CodeRabbit-8b5cf6?style=for-the-badge&logo=coderabbit&logoColor=white&labelColor=111a35)](https://coderabbit.ai)
<!-- 4 standards -->
[![HISS-21](https://img.shields.io/badge/Standards-HISS--21-06b6d4?style=for-the-badge&logo=nasa&labelColor=111a35)](AGENTS.md)
Expand Down Expand Up @@ -105,16 +105,16 @@ and output options. For compressed inputs such as MP4, use

## 🧩 Backends at a glance

[![C23](https://img.shields.io/badge/C-23-3b82f6?style=flat-square&labelColor=111a35)](docs/principles.md)
[![C++23](https://img.shields.io/badge/C%2B%2B-23-3b82f6?style=flat-square&labelColor=111a35)](docs/principles.md)
[![Go](https://img.shields.io/badge/Go-1.27-06b6d4?style=flat-square&labelColor=111a35)](go.mod)
[![Rust](https://img.shields.io/badge/Rust-2024-8b5cf6?style=flat-square&labelColor=111a35)](bindings/rust/vmafx-sys/Cargo.toml)
[![Python](https://img.shields.io/badge/Python-3.14%2B-3b82f6?style=flat-square&labelColor=111a35)](pyproject.toml)
[![CUDA](https://img.shields.io/badge/CUDA-13.4-06b6d4?style=flat-square&labelColor=111a35)](docs/backends/cuda/overview.md)
[![ROCm](https://img.shields.io/badge/ROCm-10.1-06b6d4?style=flat-square&labelColor=111a35)](docs/backends/hip/overview.md)
[![SYCL](https://img.shields.io/badge/SYCL-oneAPI-06b6d4?style=flat-square&labelColor=111a35)](docs/backends/sycl/overview.md)
[![Metal](https://img.shields.io/badge/Metal-Apple_Silicon-06b6d4?style=flat-square&labelColor=111a35)](docs/backends/metal/index.md)
[![FFmpeg](https://img.shields.io/badge/FFmpeg-n9.0.2-8b5cf6?style=flat-square&labelColor=111a35)](docs/usage/ffmpeg.md)
[![C23](https://img.shields.io/badge/C-23-3b82f6?style=for-the-badge&labelColor=111a35)](docs/principles.md)
[![C++23](https://img.shields.io/badge/C%2B%2B-23-3b82f6?style=for-the-badge&labelColor=111a35)](docs/principles.md)
[![Go](https://img.shields.io/badge/Go-1.27-06b6d4?style=for-the-badge&labelColor=111a35)](go.mod)
[![Rust](https://img.shields.io/badge/Rust-2024-8b5cf6?style=for-the-badge&labelColor=111a35)](bindings/rust/vmafx-sys/Cargo.toml)
[![Python](https://img.shields.io/badge/Python-3.14%2B-3b82f6?style=for-the-badge&labelColor=111a35)](pyproject.toml)
[![CUDA](https://img.shields.io/badge/CUDA-13.4-06b6d4?style=for-the-badge&labelColor=111a35)](docs/backends/cuda/overview.md)
[![ROCm](https://img.shields.io/badge/ROCm-10.1-06b6d4?style=for-the-badge&labelColor=111a35)](docs/backends/hip/overview.md)
[![SYCL](https://img.shields.io/badge/SYCL-oneAPI-06b6d4?style=for-the-badge&labelColor=111a35)](docs/backends/sycl/overview.md)
[![Metal](https://img.shields.io/badge/Metal-Apple_Silicon-06b6d4?style=for-the-badge&labelColor=111a35)](docs/backends/metal/index.md)
[![FFmpeg](https://img.shields.io/badge/FFmpeg-n9.0.2-8b5cf6?style=for-the-badge&labelColor=111a35)](docs/usage/ffmpeg.md)

Every GPU-backed feature extractor has at least one device twin, and each twin
is held to the CPU reference by the cross-backend parity gate. The coverage
Expand Down
5 changes: 5 additions & 0 deletions changelog.d/changed/readme-badges-uniform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- **Every README badge but the Praetor-managed governance badge now has the same `for-the-badge` design, and the status badges come from this project's own render.**
`.github/workflows/readme-badges.yml` draws the seven workflow badges and the OpenSSF Scorecard,
OpenSSF Best Practices and Codacy badges every 15 minutes onto the `badges` branch: a workflow badge
shows the newest finished master push run, so a run in progress never hides a red one, and the
Scorecard badge now shows the score. See `docs/development/readme-badges.md` (ADR-3519).
Loading
Loading