Repository navigation
PPF-2106: Validate the exact release revision and artifact before publication - #2109
Conversation
…lication - Let other workflows call Tests and Linting. Add a `distribution` job to Tests that builds the sdist and wheel once, checks them with the new scripts/check_dist.py on Python 3.11 and 3.15, uploads them as the `dist` artifact, and records the commit and SHA-256 checksums. - Deploy runs Tests and Linting on the release commit. Its upload job, the only one with id-token: write, downloads `dist`, fails unless the checksums match the recorded ones, and uploads exactly those files. A manual run is a dry run that skips the upload. - Create Release runs Tests and Linting on the dispatched commit before creating the tag or release. - check_dist.py checks that the version agrees across file names, metadata, `__version__`, and the release tag, and that the sdist and wheel contents match HEAD byte for byte. It then installs the wheel outside the checkout, without extras and with the cli extra, and checks it there with `python -I`: imports come from site-packages, every module imports, a form is created, filled, and reopened, and the `pypdfform` command works. - Describe the gates, dry run, recovery, and local check in the release guide and pull request requirements. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Mark E3-08 done and record the baseline, implementation, confirmed policy, validation, and remaining limitations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Found one issue at [P2] Keep artifact verification active during dry runs — The Validation: built the PR in a disposable checkout and ran the distribution smoke checks on Python 3.14 with and without |
- Run the Deploy upload job on every event, so a manual dry run also downloads the checked distribution and compares its checksums. Only the PyPI upload and its summary are limited to release events. - Say so in the release guide and the E3-08 action plan entry, and note that the upload job holds id-token: write during a dry run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Fixed the P2 dry-run finding in 2bbf12c. The One trade-off: job permissions can't be conditional, so the job also holds Validation: I ran the verification step's script from the YAML with |
Resolves #2106 (action plan item E3-08, review finding R13).
Baseline
v6.1.0 was built and uploaded from
1b3b5d5b, the[skip ci]version bump commit. Tests and Linting never ran on it. The Deploy workflow ranuv build && uv publishwith no tests, no linting, and no install of the built files. Create Release ran no checks before tagging.Changes
workflow_calltrigger. Tests gains adistributionjob that builds the sdist and wheel once withuv buildand checks them withscripts/check_dist.pyon Python 3.11 and 3.15. It then uploads them as thedistartifact and records the commit and SHA-256 checksums in the run summary and as a workflow output. The job runs on every PR and push.python-publish.yml): runs Tests and Linting on the release commit. The upload job needs both and is the only job withid-token: write. It downloadsdist, fails unless the files' checksums exactly match the recorded ones, and uploads those files. The trusted-publisher workflow file is unchanged.workflow_dispatch. A manual run runs every check, including downloadingdistand comparing its checksums in the upload job. Only the PyPI upload and its summary step carryif: github.event_name == 'release'. Job permissions can't depend on the event, so the upload job also holdsid-token: writeduring a dry run, but no step that runs then publishes.contents: write.scripts/check_dist.py:__version__, and release tag (on release events) must all agree.PyPDFForm/**files tracked atHEAD, compared by git blob ID, plus.dist-info. The sdist must hold the same package files plusREADME.md,LICENSE, andpyproject.toml. Beyond those, it may contain only generated metadata and tracked top-leveltests/test*.pyfiles, which matches its current contents.[cli]. A copy of the script runs there withpython -IandPYTHON*variables removed. It requires PyPDFForm to load from that venv's site-packages and compares the installed files with the wheel. It imports every module (CLI modules only with the extra) and creates, fills, writes, and reopens a text/checkbox/dropdown form.pypdfformmust print install guidance and exit 1 without the extra. With it,--version,create blank,create field,fill, andinspect datamust all work.Maintainer decisions: gate in Deploy and also before tagging in Create Release; build and check inside the Tests workflow; the smoke test as a script in
scripts/; enforce the current sdist contents; add the guarded dry run. The partialtests/in the sdist is left for a separate packaging issue. Deploy Docs is unchanged.Validation
Harness: kept outside the repository, it ran the real script against builds of disposable clones. All 63 cases passed:
PYTHONPATHandVIRTUAL_ENVpointing at the checkoutHEADand fail only in the install checksEvery failure exited 1 with a message and no traceback from the script itself.
Workflows: actionlint 1.7.12 with ShellCheck 0.11.0 passed the four changed workflows. It also caught a misspelled output name planted in a copy, so it checks the cross-workflow wiring.
Checksum step: the Deploy verification script, taken from the YAML and run with
bash -eagainst the realdistartifact from this PR's CI, passed with the recorded checksums. It failed for a tampered wheel, an extra file, a missing sdist, empty checksums, and checksums of other files. It also failed whensha256sumwas unavailable.CI: the
distributionjob passed on this PR. It checked the sdist (84 files) and wheel (58 package files) of the PR's merge commit, and installed the wheel on Python 3.11.18 and 3.15.2, without extras and with[cli].Checks:
make test-all(468 passed, 100% coverage),make linting,ruff format --check,git diff --check, and an mkdocs build all passed, using local./venvwith standardzlib.Not yet exercised
distributionjob of the new gate. Theworkflow_callpaths, the artifact hand-off, and the checksum comparison first run after merge; a Deploy dry run onmasterbefore the next release would cover them. The tag check runs only in a real release.distributionis not a required status check until it is added to branch protection.🤖 Generated with Claude Code