From b41011b0d1744a5481eafe0d0eab4bf280a240c7 Mon Sep 17 00:00:00 2001 From: xeonvs <11463419+xeonvs@users.noreply.github.com> Date: Sat, 19 Sep 2026 14:50:23 +0200 Subject: [PATCH] Fix registry artifact dependency verification --- .github/workflows/release.yml | 13 ++++++-- .github/workflows/testpypi.yml | 11 ++++--- PLANS.md | 44 ++++++++++++++++++++++++++++ scripts/verify_registry_artifacts.sh | 8 +++++ tests/test_release_receipt.py | 1 + tests/test_testpypi_preview.py | 6 ++++ 6 files changed, 77 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b40f051..0554685 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -243,6 +243,10 @@ jobs: uv run python -c "import shutil; shutil.rmtree('dist', ignore_errors=True)" uv run python -m build --no-isolation - run: uv run twine check dist/* + - name: Export hash-locked runtime dependencies + run: >- + uv export --frozen --no-dev --no-emit-project --format requirements-txt + --output-file runtime-requirements.txt - name: Verify exact artifact version env: VERSION: ${{ steps.validate.outputs.version }} @@ -313,6 +317,7 @@ jobs: dist/ artifact-hashes.json SHA256SUMS + runtime-requirements.txt if-no-files-found: error retention-days: 7 @@ -365,7 +370,9 @@ jobs: - name: Verify and smoke exact TestPyPI artifacts env: VERSION: ${{ needs.build.outputs.version }} - run: ./scripts/verify_registry_artifacts.sh testpypi "${VERSION}" artifact-hashes.json release.yml + run: >- + ./scripts/verify_registry_artifacts.sh testpypi "${VERSION}" + artifact-hashes.json release.yml runtime-requirements.txt publish-pypi: name: publish-pypi @@ -415,7 +422,9 @@ jobs: - name: Verify and smoke exact PyPI artifacts env: VERSION: ${{ needs.build.outputs.version }} - run: ./scripts/verify_registry_artifacts.sh pypi "${VERSION}" artifact-hashes.json release.yml + run: >- + ./scripts/verify_registry_artifacts.sh pypi "${VERSION}" + artifact-hashes.json release.yml runtime-requirements.txt github-release: name: publish-github-release diff --git a/.github/workflows/testpypi.yml b/.github/workflows/testpypi.yml index 15c7c1a..e60dfd9 100644 --- a/.github/workflows/testpypi.yml +++ b/.github/workflows/testpypi.yml @@ -50,16 +50,16 @@ jobs: uv run python -c "import shutil; shutil.rmtree('dist', ignore_errors=True)" uv run python -m build --no-isolation uv run twine check dist/* - uv export --frozen --no-dev --no-emit-project --format requirements-txt --output-file /tmp/runtime-requirements.txt + uv export --frozen --no-dev --no-emit-project --format requirements-txt --output-file runtime-requirements.txt python -m venv /tmp/wheel-smoke - /tmp/wheel-smoke/bin/pip install --require-hashes --requirement /tmp/runtime-requirements.txt + /tmp/wheel-smoke/bin/pip install --require-hashes --requirement runtime-requirements.txt python scripts/install_local_artifact.py \ --python /tmp/wheel-smoke/bin/python \ --artifact "$(find dist -maxdepth 1 -name '*.whl' -print -quit)" \ --requirements /tmp/wheel-smoke-requirements.txt /tmp/wheel-smoke/bin/ocr-ci --help python -m venv /tmp/sdist-smoke - /tmp/sdist-smoke/bin/pip install --require-hashes --requirement /tmp/runtime-requirements.txt + /tmp/sdist-smoke/bin/pip install --require-hashes --requirement runtime-requirements.txt python scripts/install_local_artifact.py \ --python /tmp/sdist-smoke/bin/python \ --artifact "$(find dist -maxdepth 1 -name '*.tar.gz' -print -quit)" \ @@ -106,6 +106,7 @@ jobs: path: | dist artifact-hashes.json + runtime-requirements.txt if-no-files-found: error retention-days: 7 overwrite: true @@ -151,4 +152,6 @@ jobs: - name: Verify provenance and install immutable published artifacts env: VERSION: ${{ needs.build.outputs.version }} - run: ./scripts/verify_registry_artifacts.sh testpypi "${VERSION}" artifact-hashes.json testpypi.yml + run: >- + ./scripts/verify_registry_artifacts.sh testpypi "${VERSION}" + artifact-hashes.json testpypi.yml runtime-requirements.txt diff --git a/PLANS.md b/PLANS.md index ef6e3f7..fd2a470 100644 --- a/PLANS.md +++ b/PLANS.md @@ -6,6 +6,50 @@ before handoff or commit. Completed stable plans are indexed in ## Active Work +## Registry artifact dependency verification repair + +Status: complete; corrective PR handoff authorized +Plan Origin: execution-discovered +Release classification: release-required +Target stable version: 0.11.0 + +### Goal + +Repair the immutable registry verifier so published wheel and sdist smoke tests +install the hash-locked runtime dependency set before invoking `ocr-ci`. This +restores the TestPyPI development gate and the identical stable PyPI/TestPyPI +verification boundary without changing runtime behavior or release scope. + +### Evidence And Scope + +- Main workflow `35442945566` built and published `0.11.0.dev97`, verified its + bytes and provenance, then failed both artifact smokes because the verifier + installed distributions with `--no-deps` into empty environments. +- The build-stage smoke already exports and installs the locked runtime closure; + only the post-publication verifier omitted that input. +- Add one explicit verifier argument for a generated hash-locked requirements + file, pass it from development and stable workflows, and lock the contract in + workflow/verifier tests. No dependency, federation, DLP, receipt or release + authorization semantics change. + +### Acceptance And Closure + +- Focused verifier/workflow tests and `git diff --check` pass. +- Full repository quality and hosted exact-head checks pass before protected merge. +- The feature branch is pushed only after the local commit is complete; the fix PR + uses protected merge and a subsequent main development workflow must complete. +- Archive this short corrective plan into the v0.11.0 release history and remove it + from active work in the release-preparation commit. + +### Completion Evidence + +The verifier now requires a checked-in-workflow-generated, hash-locked runtime +requirements artifact and installs it into both fresh registry smoke environments +before installing the immutable wheel or sdist without dependency resolution. The +development and stable workflows transfer that exact requirements file alongside +the reviewed distributions; focused workflow/verifier tests pass. Hosted exact-head +checks and the repaired main development publication remain the protected PR gates. + ## v0.11.0 governed MCP federation and feature draft Status: complete; release handoff authorized diff --git a/scripts/verify_registry_artifacts.sh b/scripts/verify_registry_artifacts.sh index 091e73b..7ab157d 100755 --- a/scripts/verify_registry_artifacts.sh +++ b/scripts/verify_registry_artifacts.sh @@ -5,6 +5,12 @@ registry=${1:?registry is required} version=${2:?version is required} hashes=${3:?hash file is required} workflow=${4:?expected publisher workflow is required} +runtime_requirements=${5:?hash-locked runtime requirements are required} + +test -f "${runtime_requirements}" || { + echo "runtime requirements not found: ${runtime_requirements}" >&2 + exit 2 +} case "${workflow}" in testpypi.yml|release.yml) ;; @@ -124,9 +130,11 @@ while IFS="${tab}" read -r provenance_url filename; do done < "${provenance_downloads}" python -m venv "${wheel_environment}" +"${wheel_environment}/bin/pip" install --require-hashes --requirement "${runtime_requirements}" "${wheel_environment}/bin/pip" install --no-deps "${destination}"/*.whl "${wheel_environment}/bin/ocr-ci" --help python -m venv "${sdist_environment}" +"${sdist_environment}/bin/pip" install --require-hashes --requirement "${runtime_requirements}" python scripts/install_local_artifact.py \ --python "${sdist_environment}/bin/python" \ --artifact "$(find "${destination}" -maxdepth 1 -name '*.tar.gz' -print -quit)" \ diff --git a/tests/test_release_receipt.py b/tests/test_release_receipt.py index 7bd085d..f0b37eb 100644 --- a/tests/test_release_receipt.py +++ b/tests/test_release_receipt.py @@ -369,6 +369,7 @@ def test_release_workflow_builds_reads_back_and_recovers_the_receipt() -> None: assert "verify_registry_provenance.py" in ( ROOT / "scripts" / "verify_registry_artifacts.sh" ).read_text(encoding="utf-8") + assert "runtime-requirements.txt" in workflow assert "python scripts/release_receipt.py" in workflow assert "python scripts/github_release_api.py ensure" in workflow assert "python scripts/github_release_api.py upload" in workflow diff --git a/tests/test_testpypi_preview.py b/tests/test_testpypi_preview.py index b7bac7c..d1b2e0e 100644 --- a/tests/test_testpypi_preview.py +++ b/tests/test_testpypi_preview.py @@ -175,6 +175,7 @@ def test_workflow_automates_one_idempotent_development_build_per_main_run() -> N assert "${GITHUB_RUN_NUMBER}" in workflow assert "development-version" in workflow assert workflow.count("testpypi-development-distributions") == 3 + assert "runtime-requirements.txt" in workflow assert "overwrite: true" in workflow assert "needs.build.outputs.publish == 'true'" in workflow assert "needs.publish.result == 'skipped'" in workflow @@ -195,6 +196,7 @@ def test_workflow_bounds_and_verifies_every_testpypi_download() -> None: assert workflow.count("--proto '=https' --proto-redir '=https'") == 1 assert "verify_registry_artifacts.sh testpypi" in workflow assert "artifact-hashes.json testpypi.yml" in workflow + assert "testpypi.yml runtime-requirements.txt" in workflow assert "python -m build --no-isolation" in workflow @@ -228,6 +230,7 @@ def test_production_release_verifies_reviewed_registry_artifacts() -> None: assert "attestations: true" in workflow assert workflow.count("verify_registry_artifacts.sh") == 2 assert workflow.count("artifact-hashes.json release.yml") == 2 + assert workflow.count("release.yml runtime-requirements.txt") == 2 assert workflow.count('python: ["3.12", "3.13", "3.14"]') == 2 assert "python scripts/github_release_api.py ensure" in workflow assert "python scripts/github_release_api.py upload" in workflow @@ -243,6 +246,9 @@ def test_production_release_verifies_reviewed_registry_artifacts() -> None: assert "--max-filesize 1048576" in verifier assert "--proto '=https' --proto-redir '=https'" in verifier assert "sha256sum --check --strict" in verifier + assert ( + verifier.count('pip" install --require-hashes --requirement "${runtime_requirements}"') == 2 + ) assert "verify_registry_provenance.py" in verifier assert '--workflow "${workflow}"' in verifier assert "application/vnd.pypi.integrity.v1+json" in verifier