Skip to content

build(deps): single-source package versions and unify python dependencies (ADR-1236) - #1416

Closed
lusoris wants to merge 10 commits into
masterfrom
build/version-single-source-tree
Closed

lusoris wants to merge 10 commits into
masterfrom
build/version-single-source-tree

Conversation

@lusoris

@lusoris lusoris commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The vmaf Python runtime dependency list was maintained in three files. Make python/pyproject.toml its owner, derive python/requirements.txt, remove the duplicate setup.py list, and exclude the generated requirements file from Renovate. A local/CI hook checks ordered dependency entries.

Add checks for release-owned product-version and Python CI mirrors. Align the existing vmaf-tune package marker and dev Optuna requirement. Scientific dependency floors remain owned by package manifests; five initially proposed global variables had no consumers or checks and have been removed.

Rebased onto the current #1396 hygiene parent. The reviewed tree preserves its newer NumPy 2.5.3, PyWavelets 1.10.0 and MCP requirements, the repaired Renovate patterns, both base-image regression hooks, and Level Zero container validation. Native ORT archive roles remain separate from Python dependency floors and are documented in docs/development/base-images.md.

Fix pre-push mypy selection to enforce the existing AGENTS.md §12.10 merge-base policy. A rebase can leave a branch-owned Python file unchanged while changing the type of an imported function on master; the old-tip/new-tip filename list then omits the file that must be rechecked. The hook derives the complete ai//scripts/ Python set, includes Git type changes, preserves safe internal symlink names and rejects outgoing refs different from the checked-out HEAD. Twelve real-Git regressions include execution through installed pre-commit with an empty outgoing diff.

This PR remains a held draft. Full integration acceptance is pending on #1396; the merge train must not promote or merge this stack.

Validation

  • 17 base-image/Level Zero tests and three Renovate pattern tests pass. The new entrypoint regression fails when the workflow-checker refactor drops container validation.
  • Actual requirements generation/check passes with all 13 current runtime dependencies. The workflow/version checker passes against the reviewed tree.
  • Twelve mypy scope regressions pass, including actual pre-commit invocation across a real Git rebase and empty outgoing diff. Strict mypy passes all 17 Python files owned by the combined branch against master merge-base 78c9d2bfc.
  • Normal pre-commit, commit-message and pre-push hooks pass without bypass, including formatting, shell checks, generated docs, FFmpeg cumulative replay, configuration guards and strict MkDocs. The PR body independently passes the deliverables validator against current stacked parent 851d53dc8.
  • Full make lint and make test, package build/runtime acceptance and hosted checks remain pending. A metadata gate is not scientific training validation.

Bug-status hygiene

  • docs/state.md records the unused-owner and rebase-consumer correction.
  • docs/state.md records the pre-push rebase-scope correction, which implements the existing touched-file policy (no new ADR needed).
  • Netflix golden-score assertions are unchanged.

Deep-dive deliverables

  • Research digest — docs/research/1236-version-single-source-tree.md, updated to distinguish the original inventory from the reviewed ownership boundaries.
  • Decision matrix — docs/adr/1236-version-single-source-tree.md.
  • AGENTS.md invariant note — compat/python-vmaf/AGENTS.md records dependency ownership and generated requirements; scripts/AGENTS.md records complete merge-base Python selection and fail-closed ref/path checks.
  • Reproducer / smoke-test command — below.
  • CHANGELOG fragment — changelog.d/changed/version-single-source-tree.md and changelog.d/fixed/pre-push-mypy-merge-base-scope.md.
  • Rebase note — docs/rebase-notes.md records the metadata and consumer contracts.

Reproducer

make python-deps-sync
python3 scripts/ci/check-workflow-versions.py
python3 -m unittest discover -s scripts/ci/tests -p 'test_*single_source.py'
python3 -m unittest discover -s scripts/ci/tests -p test_renovate_file_patterns.py
python3 scripts/git-hooks/test-pre-push-mypy.py
python3 scripts/git-hooks/pre-push-mypy.py

@lusoris
lusoris changed the base branch from master to build/base-image-single-source September 8, 2026 12:46
@lusoris
lusoris force-pushed the build/base-image-single-source branch 5 times, most recently from 51fb602 to ea11584 Compare September 8, 2026 15:34
lusoris and others added 8 commits September 8, 2026 18:17
…ADR-1231)

Container base images were pinned by hand at each point of use and drifted.
`Dockerfile.controller` and `Dockerfile.operator` still built on Debian 12 and
shipped on distroless-debian12 after the rest of the tree moved to Debian 13,
and both quoted a golang digest in their header comments that did not match
their own FROM line. Two CUDA pins sat on Ubuntu 24.04 beside a sibling stage
on 26.04.

Four further pins were invisible to any FROM scan because they lived in
`COPY --from=<image>` -- the Go toolchain in dev/Containerfile and the CUDA,
ROCm and oneAPI runtime libraries in Dockerfile.node. Those were the most stale
images in the repository, which is the argument both for gating `COPY --from`
and for converting them to named stages.

build-config.env is now the single source of truth. No Dockerfile names a base
directly: each takes it as an ARG whose default mirrors the config, so a plain
`docker build` still works with no wrapper and CI can override any base with
--build-arg. `scripts/ci/check-base-image-single-source.sh` fails on drift, on
a pin that is not digest-pinned, and on a version knob that disagrees with its
pin; `make base-images-sync` rewrites the mirrors. Same authority-plus-drift-
check shape as check-default-model-single-source.sh.

Removes four Debian 12 pins and two Ubuntu 24.04 CUDA pins. `vmafx-controller`
gains an explicit `USER 65532:65532`: its old cc-debian12 pin lacked the
`:nonroot` suffix, so it ran as root. Both its listen ports are above 1024.

ROCm and oneAPI keep Ubuntu 24.04 under explicit, self-closing exemptions.
Each is a major SDK migration needing matching source changes, not a pin swap:
ROCm 7.2.4 -> 10.0.0 is PR #1386, and oneAPI 2025 -> 2026.1 is blocked on a
libsycl soname bump (.so.8 -> .so.9), a glibc gap between Intel's Ubuntu images
and Debian 13, and the NEO GPU driver that Intel's runtime image carries and
Debian does not package. All measured; see the research digest.

Also fixes a latent bug in dev/Containerfile: `ENV PYTHONPATH=/build/vmaf/python:${PYTHONPATH}`
expanded to a trailing colon because PYTHONPATH was never set, putting the
current directory on sys.path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ovate

The first commit unified container base images. That was half the problem: the
same versions live in CI workflows, and they had drifted there independently.

ROCm was 7.2.4 in build.yml and 7.2.3 in libvmaf-build-matrix.yml, so CI
validated a ROCm the published images never shipped. The Level Zero loader
existed at FOUR versions simultaneously -- v1.18.5, v1.28.0 twice, v1.29.0 and
1.32.0 -- and a skew there does not fail a build. It surfaces at runtime as
"No device of requested type available", which reads like a driver or
permissions problem and is not.

build-config.env now carries the toolchain versions too (ROCM_APT_VERSION,
LEVEL_ZERO_VERSION, CUDA_APT_PACKAGE, PYTHON_CI_VERSION). Workflow `run:` steps
source it directly; scripts/ci/load-build-config.sh exports every knob into
$GITHUB_ENV for steps that need a value earlier than that. The Windows SYCL leg
runs under cmd and cannot source it, so it mirrors the value by hand and the
gate keeps that mirror honest.

scripts/ci/check-workflow-versions.py enforces it. It is a separate script
because the Level Zero clone puts `--branch vX.Y.Z` and the repository URL on
different lines, so a line-oriented grep silently sees only the first line --
the first version of this check passed against a deliberately planted literal.

Renovate is reconfigured to match. Its `dockerfile` manager understands
`ARG X=image` + `FROM $X`, so left alone it would bump the Dockerfile mirrors
and leave build-config.env behind -- failing this change's own gate on
Renovate's PRs. One custom manager now matches both the config line and the ARG
form across all nine wired files (41 pins), so a bump lands every copy in one
PR, and a packageRule disables the built-in manager on exactly those files.
docker/dev/*.Dockerfile deliberately keeps the built-in manager.

The gate also classifies config keys by the SHAPE of their value rather than by
a list of names, so adding a version knob cannot accidentally subject it to the
digest-pinning rule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ADR-1225 landed ROCm 10.0.0 on the ubuntu-24.04 variant, which left the tree
with the last release images on a distro everything else had moved off.

AMD publishes rocm/dev-ubuntu-26.04:10.0.0-full too, but this is not a pin swap
to make casually: Dockerfile.node and Dockerfile.production-gpu COPY these
libraries out of the vendor image into a Debian 13 runtime, so a libc mismatch
does not fail the build -- it fails the load, on the user's machine, with a
"cannot open shared object file" that points at the wrong thing.

Measured before moving:

  /opt/rocm/core-10.0/lib layout   identical in both variants
  max GLIBC symbol in the closure  2.28 in both (AMD builds to an old floor)
  Debian 13 runtime provides       2.41

So the 26.04 variant is a drop-in. This is the OPPOSITE of the oneAPI case,
where Intel's Ubuntu 26.04 image requires glibc 2.43 and genuinely cannot be
copied onto Debian 13 -- the question has to be asked per vendor rather than
answered once for all of them.

With ROCm current, the gate's Ubuntu-24.04 exemption list is down to the two
oneAPI entries, which disappear with the 2026.1 migration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
build-config.env was added to stop version drift, but the oneAPI knob was
deliberately left out with a comment saying the 2026.1 migration was "the
immediate follow-up to this file". That follow-up never happened, so three
workflows kept spelling `intel-oneapi-compiler-dpcpp-cpp-2025.3` inline and the
CI toolchain sat two majors behind the compiler installed on the workstation.

All three already sourced build-config.env for LEVEL_ZERO_VERSION, so the
plumbing existed; only the version was missing from it. They now read
${ONEAPI_APT_PACKAGE}, and ONEAPI_VERSION is 2026.1.

The config also gains ONEAPI_RUNTIME_APT_PACKAGES, which names
intel-oneapi-umf explicitly: intel-oneapi-runtime-dpcpp-cpp does not depend on
it, so libumf.so.1 goes missing and the adapter fails to load -- surfacing as
"No device of requested type available" rather than as a link error. That was
measured while preparing the migration, and is the kind of fact the single
source exists to keep.

The Windows leg stays at 2025.3.0.372 on purpose. Its offline-installer URL
carries an opaque per-build GUID that cannot be derived from a version number,
so bumping it needs a looked-up URL rather than a string substitution; it is
recorded as ONEAPI_WINDOWS_VERSION so the lag is visible in the same file
instead of buried in a workflow, and tracked as
T-ONEAPI-WINDOWS-CI-LAG-2026-09-07.

Verified: load-build-config.sh resolves ONEAPI_APT_PACKAGE to
intel-oneapi-compiler-dpcpp-cpp-2026.1, no literal 2025.3 oneAPI package name
remains in .github/workflows/, and both check-workflow-versions.py and
check-base-image-single-source.sh pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…m-image

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(ffmpeg): replay percentile guard after the existing mappings

* docs(adr): define stable-release FFmpeg patch maintenance

* fix(ffmpeg): automate stable-release patch refresh and required replay

* fix(go): confine predictor cards and require Go validation

* docs(adr): preserve work during agent-state cleanup

* fix(dev): preserve work during agent-state cleanup

* fix(ci): recognize root build config in dependency PRs

* fix(hooks): preserve dispatch across worktree removal

* fix(ci): reject unpinned container image bypasses

* fix(docs): refresh ADR metadata and enforce source freshness

* fix(ci): align shared config consumers and combined documentation

* fix(deps): restore Renovate custom manager file matching

* fix(docs): require push toolchain and repair guide links

* test(ffmpeg): type patch receipts and safety fixtures for push checks

* test(ci): type impact and Go workflow contracts

* docs: repair topic links and identify unavailable evidence

* fix(build): consume the shared Level Zero version

* docs: refresh FFmpeg guide and correct PSNR evidence

* fix(dev): make container shell failures explicit

* fix(hooks): retain documentation checks on large diffs

---------

Co-authored-by: Lusoris <lusoris@pm.me>
@lusoris
lusoris force-pushed the build/base-image-single-source branch from 834f840 to 851d53d Compare September 8, 2026 16:22
…cies (ADR-1236)

Single-source package versions across the repository and eliminate
Python dependency triplication:

- python/pyproject.toml [project].dependencies is now the single owner
  of runtime dependencies for the vmaf Python package. Duplicate
  install_requires in python/setup.py is removed.
- python/requirements.txt is mechanically derived via
  scripts/ci/check-python-requirements-single-source.sh --write (make python-deps-sync),
  enforced by CI and pre-commit hooks.
- renovate.json ignores python/requirements.txt to prevent dual-PR race
  conditions between pip_requirements and pep621.
- Divergent package pins are unified following the newest-version policy:
  numpy (>=2.5.2 across 10 sites and build-system), scipy (>=1.18.1),
  matplotlib (>=3.11.1), pyarrow (>=25.0.1).
- Synchronized package metadata versions: tools/vmaf-tune/pyproject.toml
  bumped to 0.0.2 matching src/vmaftune/__init__.py.
- Authoritative VMAFX_VERSION, NUMPY_VERSION, SCIPY_VERSION, MATPLOTLIB_VERSION,
  PYARROW_VERSION, and ONNXRUNTIME_VERSION added to build-config.env,
  with cross-file drift checked by scripts/ci/check-workflow-versions.py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lusoris
lusoris force-pushed the build/version-single-source-tree branch from d71e433 to 99b5d73 Compare September 8, 2026 17:08
Base automatically changed from build/base-image-single-source to master September 15, 2026 21:08
@lusoris

lusoris commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by the pre-rc.1 fixing train in #1425.

Verified by comparing this branch's tree against the train for every path its commits touch: zero differences. Its own commits are on the train already, and everything else it carried was the base-image chain that #1396's squash put on master on 2026-09-15.

Closing is safe; nothing here is lost. If you would rather keep it open until #1425 merges, that works too — it just has nothing left to contribute.

@lusoris

lusoris commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by the pre-rc.1 fixing train in #1425.

Closing because the ADR Collision Guard — a required check — fails #1425 while both PRs claim ADR-1236. Verified before closing:

The branch is untouched, so this is reversible.

@lusoris lusoris closed this Sep 15, 2026
@lusoris
lusoris deleted the build/version-single-source-tree branch September 18, 2026 07:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant