diff --git a/.github/wheel-constraints/nemo-platform-plugin.txt b/.github/wheel-constraints/nemo-platform-plugin.txt new file mode 100644 index 0000000000..bf460354d0 --- /dev/null +++ b/.github/wheel-constraints/nemo-platform-plugin.txt @@ -0,0 +1,29 @@ +# Partial dependency constraints for the CI wheel install-smoke-test. +# Pins the DIRECT external deps of the built nemo-platform-plugin wheel to vetted versions for +# reproducibility; deep transitives resolve normally (so they stay py3.14-compatible and +# cannot self-conflict). litellm is capped <1.92 — its 1.92.0 native build has no py3.14 wheel. +# Regenerate with: script/compile-wheel-constraints.sh +# +anthropic==0.116.0 +anyio==4.14.2 +distro==1.9.0 +docker==7.2.0 +fastapi==0.139.0 +fsspec==2026.6.0 +httpx==0.28.1 +jsonschema==4.26.0 +lark==1.3.1 +litellm<1.92 # 1.92.0 native build has no py3.14 wheel +ngcsdk==4.20.1 +nvidia-ml-py==13.610.43 +openai==2.45.0 +prompt-toolkit==3.0.52 +psutil==7.2.2 +pydantic-settings==2.14.2 +pydantic==2.13.4 +pyyaml==6.0.3 +requests==2.34.2 +rich==15.0.0 +sniffio==1.3.1 +typer==0.25.1 +typing-extensions==4.16.0 diff --git a/.github/wheel-constraints/nemo-platform-services.txt b/.github/wheel-constraints/nemo-platform-services.txt new file mode 100644 index 0000000000..888cbc4b88 --- /dev/null +++ b/.github/wheel-constraints/nemo-platform-services.txt @@ -0,0 +1,98 @@ +# Partial dependency constraints for the CI wheel install-smoke-test. +# Pins the DIRECT external deps of the built nemo-platform[services] wheel to vetted versions for +# reproducibility; deep transitives resolve normally (so they stay py3.14-compatible and +# cannot self-conflict). litellm is capped <1.92 — its 1.92.0 native build has no py3.14 wheel. +# Regenerate with: script/compile-wheel-constraints.sh +# +aioboto3==15.5.0 +aiofiles==25.1.0 +aiohttp==3.14.1 +aiosqlite==0.22.1 +alembic==1.18.5 +anthropic==0.116.0 +anyio==4.14.2 +asyncpg==0.31.0 +base58==2.1.1 +boto3==1.40.61 +botocore==1.40.61 +clickhouse-connect==0.15.1 +cloudpickle==3.1.2 +data-designer==0.6.1 +datasets==4.3.0 +distro==1.9.0 +docker==7.2.0 +duckdb==1.5.4 +fastapi==0.139.0 +fastmcp==3.4.4 +fsspec==2025.9.0 +greenlet==3.5.3 +gunicorn==26.0.0 +httpx==0.28.1 +huggingface-hub==1.23.0 +hvac==2.4.0 +jinja2==3.1.6 +jsonpath-ng==1.8.0 +jsonschema==4.26.0 +kubernetes==36.0.2 +langchain-aws==1.1.0 +langchain-community==0.3.31 +langchain-core==1.4.9 +langchain-nvidia-ai-endpoints==1.4.3 +langchain-openai==1.3.5 +langchain==1.3.13 +lark==1.3.1 +litellm<1.92 # 1.92.0 native build has no py3.14 wheel +nemo-anonymizer==0.2.1 +nemo-safe-synthesizer==0.1.2 +nemoguardrails==0.23.0 +ngcsdk==4.20.1 +nvidia-ml-py==13.610.43 +nvidia-nat-config-optimizer==1.8.0 +nvidia-nat-core==1.8.0 +nvidia-nat-langchain==1.8.0 +openai==2.45.0 +opentelemetry-distro==0.64b0 +opentelemetry-exporter-otlp-proto-grpc==1.43.0 +opentelemetry-exporter-otlp-proto-http==1.43.0 +opentelemetry-exporter-otlp==1.43.0 +opentelemetry-exporter-prometheus==0.64b0 +opentelemetry-instrumentation-fastapi==0.64b0 +opentelemetry-instrumentation-httpx==0.64b0 +opentelemetry-instrumentation-requests==0.64b0 +opentelemetry-instrumentation-sqlalchemy==0.64b0 +opentelemetry-instrumentation-system-metrics==0.64b0 +opentelemetry-processor-baggage==0.64b0 +opentelemetry-proto==1.43.0 +opentelemetry-sdk==1.43.0 +pandas==2.3.3 +prometheus-client==0.25.0 +prometheus-fastapi-instrumentator==8.0.2 +prompt-toolkit==3.0.52 +psutil==7.2.2 +psycopg2-binary==2.9.12 +pyarrow==22.0.0 +pydantic-settings==2.14.2 +pydantic==2.13.4 +pyjwt==2.13.0 +pyleak==0.2.0 +python-multipart==0.0.32 +pyyaml==6.0.3 +ragas==0.4.3 +requests==2.34.2 +rich==14.3.4 +rouge-score==0.1.2 +sacrebleu==2.6.0 +sniffio==1.3.1 +sqlalchemy==2.0.51 +sqlmodel==0.0.39 +streaming-form-data==2.1.0 +structlog==26.1.0 +tenacity==9.1.4 +typer==0.25.1 +types-aioboto3==15.5.0 +typing-extensions==4.16.0 +urllib3==2.7.0 +uvicorn==0.51.0 +wasmtime==46.0.1 +xdg-base-dirs==6.0.2 +yara-python==4.5.1 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ed8612042f..85121f2e90 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1087,9 +1087,18 @@ jobs: exit 1 fi WHEEL="${wheels[0]}" - uv tool install --force --python "${PYTHON_VERSION}" "${WHEEL}[services]" + # Supply-chain: partial-pin the wheel's direct external deps to a committed constraints + # file (deep transitives resolve normally, so they stay py3.14-compatible and can't + # self-conflict), and cap the litellm transitive whose 1.92.0 native build has no py3.14 + # wheel. Regenerate with script/compile-wheel-constraints.sh. + VENV="${RUNNER_TEMP}/wheelcheck/.venv" + uv venv "${VENV}" --python "${PYTHON_VERSION}" + uv pip install --python "${VENV}/bin/python" \ + --constraint "${GITHUB_WORKSPACE}/.github/wheel-constraints/nemo-platform-services.txt" \ + "${WHEEL}[services]" cd "${RUNNER_TEMP}/wheelcheck" unset PYTHONPATH VIRTUAL_ENV + export PATH="${VENV}/bin:${PATH}" bash "${GITHUB_WORKSPACE}/script/test-nemo-cli.sh" - name: Install + test nemo-platform-plugin @@ -1106,9 +1115,13 @@ jobs: exit 1 fi WHEEL="${wheels[0]}" + # Supply-chain: partial-pin direct external deps via a committed constraints file + # (see the nemo-platform CLI step above). cd "${RUNNER_TEMP}/wheelcheck" uv venv .venv --python "${PYTHON_VERSION}" - uv pip install --python .venv/bin/python "${WHEEL}" + uv pip install --python .venv/bin/python \ + --constraint "${GITHUB_WORKSPACE}/.github/wheel-constraints/nemo-platform-plugin.txt" \ + "${WHEEL}" unset PYTHONPATH VIRTUAL_ENV # Import the package + a couple of representative submodules # (cli, commands). These are the surfaces a plugin author would diff --git a/script/compile-wheel-constraints.sh b/script/compile-wheel-constraints.sh new file mode 100755 index 0000000000..3bd03a57f5 --- /dev/null +++ b/script/compile-wheel-constraints.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Regenerate the partial dependency constraints used by CI's wheel install-smoke-test +# (.github/wheel-constraints/*.txt). +# +# Why partial (constraints, not a full lock): the smoke test installs the freshly +# built nemo-platform / nemo-platform-plugin wheels. Without any pin it resolves +# the whole tree fresh from PyPI (non-reproducible, supply-chain risk). A FULL +# lock does not work here — the vendored-SDK wheel needs newer deps than +# uv.lock, and pinning the entire tree also pins packages that lack py3.14 +# wheels, breaking the py3.14 matrix. So we pin only each wheel's DIRECT external +# deps (the versions we've vetted) and let deep transitives resolve normally, so +# they stay py3.14-compatible and cannot self-conflict. +# +# litellm is capped <1.92 (a transitive): litellm 1.92.0 ships a native/PyO3 +# build with no py3.14 wheel. Drop the cap once litellm ships a 3.14 wheel. +# +# Usage: +# script/compile-wheel-constraints.sh +# +# Wheels come from `uv build --package nemo-platform[-plugin]` (nemo-platform +# needs the Studio/node toolchain) or a CI "-wheel-py3.11" artifact +# (`gh run download -n nemo-platform-wheel-py3.11 -D `). +set -euo pipefail + +WHEEL_DIR="${1:?usage: script/compile-wheel-constraints.sh }" +OUT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/.github/wheel-constraints" +LITELLM_CAP='litellm<1.92 # 1.92.0 native build has no py3.14 wheel' + +# Pin each of a wheel's direct external deps to the version that resolves for it +# (with litellm capped), then append the litellm cap. +emit_constraints() { + local wheel="$1" spec="$2" label="$3" out="$4" venv meta + venv="$(mktemp -d)" + meta="$(mktemp -d)" + uv venv "${venv}" --python 3.11 --quiet + # Resolve+install once with the cap so we snapshot consistent, py3.14-safe versions. + printf '%s\n' "${LITELLM_CAP%% *}" >"${meta}/cap.txt" + uv pip install --python "${venv}/bin/python" --constraint "${meta}/cap.txt" "${spec}" >/dev/null + # Direct external deps = the wheel's own Requires-Dist, minus self-referential extras. + python3 - "$wheel" >"${meta}/names.txt" <<'PY' +import sys, zipfile, re +names=set() +with zipfile.ZipFile(sys.argv[1]) as z: + md=next(n for n in z.namelist() if n.endswith(".dist-info/METADATA")) + for line in z.read(md).decode().splitlines(): + if line.startswith("Requires-Dist:"): + dep=line.split(":",1)[1].strip() + name=re.split(r"[<>=!~;\[ ]", dep, 1)[0].strip() + if name and not name.startswith("nemo-platform"): + names.add(name) +print("\n".join(sorted(names))) +PY + { + printf '# Partial dependency constraints for the CI wheel install-smoke-test.\n' + printf '# Pins the DIRECT external deps of the built %s wheel to vetted versions for\n' "${label}" + printf '# reproducibility; deep transitives resolve normally (so they stay py3.14-compatible and\n' + printf '# cannot self-conflict). litellm is capped <1.92 — its 1.92.0 native build has no py3.14 wheel.\n' + printf '# Regenerate with: script/compile-wheel-constraints.sh \n#\n' + while read -r name; do + [[ -n "${name}" ]] || continue + ver="$("${venv}/bin/python" -c "import importlib.metadata as m; print(m.version('${name}'))" 2>/dev/null || true) + [[ -n "${ver}" ]] && printf '%s==%s\n' "${name}" "${ver}" + done <"${meta}/names.txt" | sort + printf '%s\n' "${LITELLM_CAP}" + } >"${out}" + rm -rf "${venv}" "${meta}" + echo "wrote ${out} ($(grep -cE '^[a-z0-9].*(==|<[0-9])' "${out}") pins)" +} + +np_wheel="$(find "${WHEEL_DIR}" -name 'nemo_platform-*.whl' | head -1)" +pl_wheel="$(find "${WHEEL_DIR}" -name 'nemo_platform_plugin-*.whl' | head -1)" +[[ -n "${np_wheel}" ]] || { echo "no nemo_platform-*.whl in ${WHEEL_DIR}" >&2; exit 1; } +[[ -n "${pl_wheel}" ]] || { echo "no nemo_platform_plugin-*.whl in ${WHEEL_DIR}" >&2; exit 1; } + +emit_constraints "${np_wheel}" "${np_wheel}[services]" "nemo-platform[services]" "${OUT_DIR}/nemo-platform-services.txt" +emit_constraints "${pl_wheel}" "${pl_wheel}" "nemo-platform-plugin" "${OUT_DIR}/nemo-platform-plugin.txt"