CI overview¶
This page documents the fork's CI surface for contributors. The authoritative trigger / gate behaviour lives in the workflow files under .github/workflows/; this doc explains the rules a contributor needs to know without reading every file.
Workflows¶
The fork ships eight pull_request-triggered workflows:
| File | Purpose |
|---|---|
docker-image.yml | Docker image build (advisory). |
security-scans.yml | Semgrep / CodeQL / Gitleaks / Dependency Review. |
lint-and-format.yml | Pre-commit, clang-tidy, cppcheck, mypy, registry validate. |
required-aggregator.yml | Single required-check aggregator (ADR-0313). |
ffmpeg-integration.yml | FFmpeg + libvmaf build (gcc / clang / SYCL / Vulkan). |
libvmaf-build-matrix.yml | Cross-platform / cross-backend libvmaf build matrix. |
rule-enforcement.yml | ADR-0100 / 0106 / 0108 / 0165 process gates. |
tests-and-quality-gates.yml | Netflix golden, sanitizers, tiny-AI, MCP, coverage, assertion-density. |
Draft pull requests do not trigger CI¶
Per ADR-0331, every pull_request-triggered workflow above is gated to skip when the PR is in draft state. Concretely:
- Each workflow's
pull_request:block liststypes: [opened, synchronize, reopened, ready_for_review]. - Each top-level job carries an
if:clause of the formgithub.event_name != 'pull_request' || github.event.pull_request.draft == false.
What this means for contributors:
- A draft PR shows no green checks. The required-checks aggregator skips on drafts and branch protection treats the missing aggregator as "required check absent". This is benign — GitHub blocks merging a draft PR by definition, so the gate cannot be bypassed.
- Promoting the draft to ready-for-review fires CI exactly once. GitHub's
ready_for_reviewevent is what re-triggers the workflows; subsequentsynchronizeevents on the now-ready PR fire CI as before. - Pushing to
masteris unaffected. The job-levelif:clause short-circuits totruewhen there is no PR object (for example onpush:events).
To preview CI status before merging, mark the PR ready-for-review. You can flip back to draft afterwards if more work is needed; the next ready_for_review will fire a fresh matrix.
Required-checks aggregator¶
The single required check on master branch protection is the Required Checks Aggregator (see ADR-0313). It runs on every non-draft PR, polls for the named sibling check_runs to reach a terminal state, and accepts success, skipped, or neutral per check. Because the aggregator itself skips on drafts, draft PRs display "missing required check" — same situation as item 1 above and unmergeable for the same reason.
Bug-status hygiene gate (ADR-0165 / ADR-0334)¶
Per CLAUDE.md §12 rule 13 and ADR-0165, every PR that closes a bug, opens a bug, or rules a Netflix upstream report not-affecting-the-fork updates docs/state.md in the same PR. Until ADR-0334 this rule was reviewer-enforced; it now runs as the state-md-touch-check job in rule-enforcement.yml, backed by the single-purpose script scripts/ci/state-md-touch-check.sh.
The gate fires when any of the following hold:
- PR title carries a Conventional-Commit
fix:orfix(scope):prefix. - PR title contains the bare token
bug(word-boundary, sodebugdoes not fire). - PR title or body contains a
closes/fixes/resolves#NGitHub-issue close keyword (case-insensitive). - PR body has the
## Bug-status hygienetemplate section with thedocs/state.mdcheckbox left unchecked.
The gate clears when either:
- The diff against
BASE_SHA..HEAD_SHAincludesdocs/state.md(the row landed in the appropriate section: Open / Recently closed / Confirmed not-affected / Deferred) AND none of the inserted lines carry a placeholder PR/commit reference (see "Placeholder-ref hardening" below), or - The PR description contains
no state delta: REASON(REASON is any non-empty token that is not the literal placeholderREASON). Use this for purefeat/refactor/infraPRs that genuinely have no bug-status impact.
Placeholder-ref hardening (ADR-0334 status update 2026-05-09). Touching docs/state.md is necessary but not sufficient. PR #541's row audit found that the dominant staleness pattern is post-merge backfill drift — closing PRs write this PR as the closer-PR placeholder, the merge happens, the placeholder never gets rewritten to the merged numeric refs. The gate therefore additionally rejects any inserted line in docs/state.md containing:
| Placeholder | Why |
|---|---|
this PR | post-merge backfill drift (most common) |
this commit | same drift mode for SHA-shaped refs |
TBD | obvious fill-it-in-later marker |
<PR> | template placeholder |
#NNN | template placeholder (real refs are digits) |
Canonical accept forms — explicitly NOT matched — are PR #N (any positive integer) and commit `<sha>` (the SHA wrapped in backticks). For an in-flight PR whose number is not yet final, you can either:
- Land the row with a placeholder, then push a follow-up commit that rewrites it to
PR #<number>aftergh pr createreturns the number, or - Use
PR #<this-pr-number>once GitHub has assigned it (the PR number is known the momentgh pr createexits).
Local dry-run (mirrors the deliverables-check.sh pattern):
PR_TITLE="fix: foo segfault" \
PR_BODY="$(gh pr view 999 --json body -q .body)" \
bash scripts/ci/state-md-touch-check.sh
Or pipe the body on stdin if gh isn't on PATH:
gh pr view 999 --json body -q .body \
| PR_TITLE="fix: foo segfault" bash scripts/ci/state-md-touch-check.sh
The companion fixture script scripts/ci/test-state-md-touch-check.sh exercises the gate against 18 cases (5 primary + 3 regression + 10 placeholder-ref). Run it after touching either script:
Local pre-flight gate¶
Before pushing, run the local subset of CI to catch the common formatter / lint / fast-test failures:
make format-check # clang-format + black + isort, no writes
make lint # clang-tidy + cppcheck + iwyu + ruff + semgrep
meson test -C build --suite=fast
pre-commit run --all-files # if .pre-commit-config.yaml hooks are installed
The format-check + pre-commit pair catches roughly the same surface as lint-and-format.yml's pre-commit job in seconds, vs. a 10-minute CI round-trip.