Skip to content

fix(release): clear the 1.0.0-rc.1 merge and publication path - #1570

Merged
lusoris merged 9 commits into
masterfrom
ci/exempt-changelog-archive-large-file
Sep 27, 2026
Merged

lusoris merged 9 commits into
masterfrom
ci/exempt-changelog-archive-large-file

Conversation

@lusoris

@lusoris lusoris commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR fixes every defect that a dry-run cut of release PR #1213 and a multi-agent gate review found on the path to merging and publishing 1.0.0-rc.1. Each fix was reviewed independently before integration.

1. The cut commit was refused (ADR-1345)

The 1.0.0-rc.1 changelog cut could not be committed. The rollover moves the fork's whole fragment history, 2,035 sources, into docs/changelog-archive/1.0.0-rc.1.md. At 1.86 MB that exceeds the 1,024 KB check-added-large-files limit, so the hook refused the cut commit on release PR #1213.

This PR exempts only ^docs/changelog-archive/[^/]+\.md$ from that hook (ADR-1345). Every other path, including nested or non-Markdown files in that directory, keeps the limit.

ADR-1115 kept the limit for a 2.1 MB generated header by generating it at build time. That route doesn't exist here: after the cut, the archive is the only remaining copy of the consumed fragments' text, and the rollover receipt pins its SHA-256.

  • New rollover test T18 checks the archive path the rollover actually writes (from T12) against the exclude pattern in .pre-commit-config.yaml, and asserts that neighbouring paths stay guarded. It fails against master's config.
  • The release guide, scripts/release/AGENTS.md, docs/state.md, the rebase notes and a changelog fragment record the exemption.

Why the earlier dry run missed this: pre-commit run --from-ref/--to-ref doesn't treat committed files as newly added. This time the cut was dry-run through a real hooked git commit on #1213's head with this change applied, and it passes (2,036 sources).

2. Further release-path fixes (found by the gate review)

  • A required test read a fragment the cut deletes. core/test/test_metal_ms_ssim_options_contract.py read changelog.d/added/T-GAP-METAL-MS-SSIM-DB-CHROMA-OPTIONS-2026-09-07.md, so the fast meson suite would fail on the release PR and turn Ubuntu gcc+DNN (a must-report release check) red. A repository sweep found no other code reading fragment contents.
  • The release-please draft check ignored -rc.N drafts. Its regex was ^vX.Y.Z$, so the v1.0.0-rc.1 draft would not pause release-please. With bootstrap-sha gone after the cut, the next master push would have re-run PR generation against an untagged release. The check now matches the verifier's shape, sets -euo pipefail itself and fails closed on an unreadable release list. A new test extracts the step from the workflow and proves parity.
  • The native artifact verifier could not pass for any release. It refused 1.0.0-rc.1, and it compared vmaf --version with the bare version although a build at the tag reports v1.0.0-rc.1-0-g<hex> (reproduced on real builds; the final 1.0.0 would fail too). It now accepts exactly the bare version or the distance-0 describe form.
  • vmaf-mcp distributions were matched by their SemVer spelling. Hatchling names them 1.0.0rc1, so mcp-build failed and SBOM, signing, PyPI and release attachment were skipped. A dependency-free scripts/release/pep440-version.sh now feeds the four globs.
  • The local pre-push PR-body hook blocked pushing the cut. It now calls CI's scripts/ci/release-pr-exempt.sh, maps gh's app/<name> author to <name>[bot], and exempts only when the PR's head ref is the pushed branch.
  • Hardening from review. Both version scripts run under LC_ALL=C, and test-verify-release-version.sh no longer fails on hosts with tag.gpgsign=true.

Type

  • CI / build tooling

Checklist

  • Commits follow Conventional Commits.
  • Pre-commit and the pre-push gate passed locally.
  • No public C API, CLI flag or FFmpeg patch surface changes.

Bug-status hygiene

  • docs/state.md updated in this PR: six Recently-closed rows (T-RELEASE-CUT-ARCHIVE-LARGE-FILE, T-RELEASE-CUT-TEST-READS-FRAGMENT, T-RELEASE-RC-DRAFT-GATE, T-RELEASE-NATIVE-VERIFY-RC-DESCRIBE, T-RELEASE-MCP-PEP440-DIST-NAMES, T-RELEASE-PREPUSH-RELEASE-PR-EXEMPTION, all 2026-09-27).

Netflix golden-data gate

  • No Netflix assertAlmostEqual(...) value was modified.

Deep-dive deliverables

  • Research digest — no digest needed: trivial (the evidence is the hook output and the archive size, recorded in ADR-1345).
  • Decision matrix — ADR-1345 ## Alternatives considered: exempt the directory's Markdown files, split the archive, or raise the global limit.
  • AGENTS.md invariant note — scripts/release/AGENTS.md (archive exclusion, version shapes, describe form), scripts/ci/AGENTS.md (pre-push exemption), .github/AGENTS.md (draft pause covers RC drafts).
  • Reproducer / smoke-test command — below.
  • CHANGELOG fragment — changelog.d/fixed/release-cut-archive-large-file.md, release-please-rc-draft-gate.md, native-release-verify-rc-describe.md, mcp-pep440-dist-names.md, pre-push-release-pr-exemption.md.
  • Rebase note — docs/rebase-notes.md entries "ADR-1345 — changelog archive large-file exemption" and "1.0.0-rc.1 release-path fixes" (2026-09-27).

Reproducer

for t in rollover-changelog-fragments release-please-draft-gate verify-native-release-artifacts pep440-version verify-release-version concat-changelog-fragments; do
  bash scripts/release/tests/test-$t.sh
done
python3 scripts/git-hooks/test-pre-push-pr-body-lint.py
python3 -B core/test/test_metal_ms_ssim_options_contract.py
# On release PR #1213's head with this change applied:
scripts/release/rollover-changelog-fragments.sh --version 1.0.0-rc.1 --date "$(date -u +%F)"
git add CHANGELOG.md changelog.d release-please-config.json docs/changelog-archive
git commit -m 'chore(release): cut 1.0.0-rc.1 changelog'   # hooks pass

The 1.0.0-rc.1 cut moves the fork's whole fragment history, 2,035 sources,
into docs/changelog-archive/1.0.0-rc.1.md (1.86 MB), and the 1 MB
check-added-large-files hook refused the cut commit. After the cut the
archive is the only remaining copy of that text, so it cannot be generated
at build time the way ADR-1115 handled the BRISQUE header.

check-added-large-files now excludes ^docs/changelog-archive/[^/]+\.md$
only (ADR-1345). Rollover test T18 checks the archive path the rollover
writes against that pattern and keeps neighbouring paths guarded; it fails
against the previous config.
@github-actions github-actions Bot added the type:ci CI and infrastructure label Sep 27, 2026
core/test/test_metal_ms_ssim_options_contract.py read
changelog.d/added/T-GAP-METAL-MS-SSIM-DB-CHROMA-OPTIONS-2026-09-07.md in
setUpClass. The 1.0.0-rc.1 cut deletes every consumed fragment, so the
fast meson suite would fail on the release PR and turn Ubuntu gcc+DNN, a
check the release PR must report, red. The 351x351 boundary stays pinned
by the Metal AGENTS.md, the MS-SSIM metric doc and Research-2110. A sweep
found no other code that reads fragment contents.

T18 now pins the exact large-file exclusion pattern, because sample paths
alone missed five widenings (lost anchors, an unescaped dot, an added
alternative). ADR-1345 now cites ADR-1233 for the archive shape, says the
receipt hashes the rendered body the archive holds, and describes ADR-1115
accurately.
…afts

release-please.yml pauses both release-please phases while one unpublished
release draft waits at the human publication gate (ADR-1127). The step that
finds that draft selected tags with a plain vX.Y.Z jq regex, so the
v1.0.0-rc.1 draft that the #1213 merge creates was invisible to it. With the
draft invisible and bootstrap-sha removed by the cut, the next master push
would re-run release-PR generation against an untagged release: it either
errors or opens a bogus release PR, instead of waiting for publication.

The filter now accepts the same narrow shape that
scripts/release/verify-release-version.sh accepts: vX.Y.Z, or vX.Y.Z-rc.N
where N has no leading zero (ADR-1201). The step name and the error text no
longer say "ordinary-SemVer". jq also writes to a file now instead of feeding
`mapfile < <(...)`. A jq failure inside process substitution was swallowed,
and the resulting empty list read as "no draft", which is the same fail-open.

scripts/release/tests/test-release-please-draft-gate.sh extracts the step and
its jq filter from the workflow and checks four things. The filter is run
against sample release lists. The filter must accept exactly the tags
verify-release-version.sh accepts, across 30 tag shapes. The full step runs
under the runner's default `bash -e` with a stub gh, and an unreadable or
malformed release list must fail closed. Both release-please invocations must
still be gated on the draft output. The Release Script Contract job runs it.
Against the previous workflow it reports 18 failures; with this change all 64
checks pass.
The draft-detection step fails closed on a jq error only because the
runner default shell runs with -e. State shell: bash explicitly so that
guarantee does not rest on the default. .github/AGENTS.md still described
the retired 3.2.1 cut; it now names the 1.0.0-rc.1 cut and the Release-As
footer later candidates use.
verify-native-release-artifacts.sh gates both the build-artifacts and
verify-native-artifacts jobs of supply-chain.yml, and it could not pass
for any release this fork can publish.

First, its VERSION argument accepted only a plain MAJOR.MINOR.PATCH, so
the 1.0.0-rc.1 that validate-release derives from the v1.0.0-rc.1 tag
exited 64 before anything was checked. ADR-1201 relaxed six guards for
release candidates and missed this one.

Second, it compared `vmaf --version` byte-for-byte with that bare
version. The CLI prints VMAF_VERSION, which core/include/meson.build
takes from `git describe --tags --long --match 'v*.*.*'`, and the
release job checks out the tag itself, so describe succeeds and the
binary reports `v<version>-0-g<hex>`. The Meson project version is only
the fallback for a checkout with no reachable tag. A real CPU-only build
from a checkout that mirrors actions/checkout v7.0.1 (unqualified tag
ref, depth 1, no tags) printed `v1.0.0-rc.1-0-gd0f0e7e` for tag
v1.0.0-rc.1 and `v1.0.0-0-g8820048` for tag v1.0.0. The unmodified
verifier rejected both, so the final 1.0.0 would have failed at the same
step.

The verifier now takes exactly the ADR-1201 shape (X.Y.Z or X.Y.Z-rc.N,
no leading zeros) and accepts exactly two reported strings: describe
exactly on the tag (distance 0, 7 to 64 lowercase hex digits) or the
bare fallback, which verify-release-version.sh already pins to the tag
through the coordinated core/meson.build marker. It still matches the
whole string, never a prefix. A non-zero distance (a commit after the
tag), a -dirty or other suffix, rc.10 offered for rc.1 and extra output
all still fail. `vmaf --version` output is unchanged.

The existing release-script-contract test now builds one fixture CLI per
reported string (printed on stderr, like cli_parse.cpp) and covers final
and candidate versions in both forms, rc.0, rc.10 and full-hash
boundaries, 13 malformed VERSION arguments, and 25 reported strings that
must be rejected. Against the old verifier 28 of the 56 cases fail.
With only the argument check relaxed, the 6 describe-form acceptances
still fail.
A release candidate could not publish vmaf-mcp. supply-chain.yml built
its wheel and sdist globs from the SemVer release version (1.0.0-rc.1),
but hatchling names the files after the PEP 440 normalized version:
vmaf_mcp-1.0.0rc1-py3-none-any.whl and vmaf_mcp-1.0.0rc1.tar.gz. For
v1.0.0-rc.1 the mcp-build hash step therefore found no wheel and failed,
which skipped the SBOM, signing, PyPI publication and release attachment
jobs behind it. Final X.Y.Z releases were unaffected because both
spellings are identical there.

validate-release now derives a pep440_version output once, through the
new scripts/release/pep440-version.sh. The converter is dependency-free
and exact for the two shapes ADR-1201 accepts (X.Y.Z unchanged,
X.Y.Z-rc.N to X.Y.ZrcN); every other input exits 64 with no output. The
version-bound wheel and sdist globs in sbom, mcp-build and both
mcp-publish-pypi steps, and the PyPI JSON release URL, now use it.

The SBOM identity checks deliberately keep the SemVer version. Hatchling
1.32.4 writes the pyproject spelling into METADATA, and Syft 1.51.1
copies it verbatim into the vmaf-mcp versionInfo and purl, so switching
them would break the release instead of fixing it.

The sdist pattern contains no wildcard, so nullglob always left one
literal entry in the array and the count check could never reject a
wrong sdist name. Each of the four version-bound checks now also
requires that the sdist exists.

scripts/release/tests/test-pep440-version.sh covers the converter's
accepted, rejected and boundary cases, the workflow wiring, and resolves
every version-bound glob against hatchling-shaped filenames with decoys.
It runs in the Release Script Contract job and fails against the
previous workflow.
The pre-push PR-body hook mirrors CI's Deliverables Checklist (ADR-0108)
but lacked the ADR-1151 release-PR exemption that CI applies through
scripts/ci/release-pr-exempt.sh. Pushing the changelog cut from the local
release-please--branches--master--components--vmafx branch therefore
failed with six "ADR-0108 missing deliverable" errors against #1213's
216-character bot body, a gate CI itself skips for that PR.

The hook now asks gh for the PR's author and headRefName and calls the
shared predicate instead of re-implementing it. gh marshals every
non-User author as {"is_bot": true, "login": "app/<x>"} (cli/cli
Author.MarshalJSON), whereas CI passes the event payload's
"<x>[bot]" / "Bot", so the hook maps that shape to the payload shape;
a human keeps its login with type "User", and any other shape (deleted
author, missing field) maps to an empty identity that never exempts.
The public-page fallback carries no author and never exempts, and a
branch without the predicate validates as before.

Tests cover the bot release PR (github-actions and a release-bot App),
a human PR on the same head ref, non-bot lookalike authors, a bot PR on
an ordinary branch or with no head ref, a missing predicate, and the
public-page fallback. The test hook now also re-runs when the predicate
changes.
- pre-push PR-body hook: gh reads a numeric branch name as a PR number,
  so a local branch named 1213 found the bot release PR and was exempted.
  The exemption now also requires the PR's head ref to be the branch being
  pushed. The sentinel guide no longer claims the public-page fallback is
  always validated; it is never exempted.
- release-please draft gate: the step sets -euo pipefail itself, and its
  test runs it under plain bash, so failing closed never depends on the
  runner's default shell.
- verify-native-release-artifacts.sh and pep440-version.sh run with
  LC_ALL=C, because [0-9] matches non-ASCII digits in some UTF-8 locales.
- test-verify-release-version.sh creates its fixture tag unsigned, so it
  passes on hosts with tag.gpgsign=true.
- scripts/release/AGENTS.md states the describe-form version accurately.
Adds the docs/state.md rows, changelog fragments and rebase notes for the
release-path defects found by the rc.1 dry-run cut, and documents in the
release guide how a candidate moves through the draft pause, version
strings, Python distribution names and the pre-push hook.
@lusoris lusoris changed the title ci(release): exempt release changelog archives from the large-file gate fix(release): clear the 1.0.0-rc.1 merge and publication path Sep 27, 2026
@github-actions github-actions Bot added type:bug Something isn't working and removed type:ci CI and infrastructure labels Sep 27, 2026
@lusoris
lusoris merged commit 8421376 into master Sep 27, 2026
112 of 119 checks passed
@lusoris
lusoris deleted the ci/exempt-changelog-archive-large-file branch September 27, 2026 09:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant