Repository navigation
Add the offline candidate boundary and publication resume for crates.io (#1065) - #1083
Conversation
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait...
|
Reviewer's GuideThe PR completes the trusted crates.io publication boundary by validating package identity and bytes offline before artifact handoff, distinguishing tagless rehearsals from tagged releases, and adding contract-backed resume behavior that skips duplicate publication and verifies the already-served archive. Sequence diagram for offline candidate validation and resumable publicationsequenceDiagram
participant Package as package-crate
participant Candidate as crates_io_candidate_cli.py
participant Artifact as release-crate-package
participant Publisher as crates.io publisher
participant Registry as crates.io
Package->>Candidate: check --rehearsal or --tag
Candidate->>Candidate: Validate identity and archive locally
Package->>Artifact: Upload validated candidate
Publisher->>Artifact: Download candidate
Publisher->>Candidate: Recheck candidate
Publisher->>Registry: Probe version
Registry-->>Publisher: absent
Publisher->>Registry: Authenticate and upload archive
Registry-->>Publisher: Publication response
Publisher->>Registry: Probe version on rerun
Registry-->>Publisher: identical
Publisher->>Registry: Download public archive
Registry-->>Publisher: Served bytes
Publisher->>Publisher: Verify archive bytes
State diagram for crates.io publication resumestateDiagram-v2
[*] --> CandidateValidated
CandidateValidated --> Probe
Probe --> Publish: absent
Publish --> Published
Published --> Probe: interrupted rerun
Probe --> VerifyExisting: identical
VerifyExisting --> Completed: bytes match
Probe --> Conflict: conflict or yanked
Completed --> [*]
Conflict --> [*]
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
9957f0d to
4d60144
Compare
bee867c to
9d1f5d9
Compare
9d1f5d9 to
b0f0499
Compare
b0f0499 to
02dd50f
Compare
|
@coderabbitai please assess the implementation in this PR for completeness and correctness against the original issue: |
|
Tip For best results, initiate chat on the files or code changes. Analysis chain
I assessed head Acceptance criteria
What
|
aaa0605 to
a0f9f56
Compare
a0f9f56 to
8cf0952
Compare
The candidate check previously demanded a release tag, so a pull-request rehearsal had no way to validate the packaged archive: a rehearsal records no tag, and inventing one would let a pull request claim a release it never built. Make the tag optional and select the mode with a required mutually exclusive group. `--tag` keeps the publishing contract, where the identity must record exactly the tag being released; `--rehearsal` accepts a tagless identity and rejects one that carries a tag. Supplying neither or both is a usage error rather than a check that silently accepts whichever identity it was handed. Co-Authored-By: Claude Code <noreply@anthropic.com>
A publication can stop after the upload but before the downloaded bytes are verified. The rerun then observes an identical version, and the publisher must stand down rather than publish again: crates.io rejects a second upload of an immutable version, so the skip is what makes the retry converge. Drive the publish sequence through that history: an absent probe decides to publish, a second identical probe decides to skip, and the verification step then confirms the served bytes match the packaged digest. Pin the same boundary in the workflow contract by requiring the skip resolution to exclude the authentication, metadata, and upload steps while retaining verification. Co-Authored-By: Claude Code <noreply@anthropic.com>
The publisher revalidates the candidate after the artefact hand-off, but nothing proved the archive and its identity record agreed while both were still local. A pull-request lane also had no rehearsal of that boundary without reaching the registry. Run the candidate check in the packaging action, immediately after the archive is produced and before it is uploaded. The step selects `--rehearsal` for a dry run and `--tag` for a publishing run, mirroring the packaging step's own mode selector, and carries no token or registry request. Extend the package contract to pin the step's id, ordering, environment, exact command, and offline nature, with a mutation case that proves the contract notices a rehearsal checked as a publishing run. Co-Authored-By: Claude Code <noreply@anthropic.com>
Record the candidate boundary the packaging action now enforces, the required release mode that distinguishes a rehearsal from a publishing run, and the resume path an interrupted publication takes. The developers' guide, ADR-0007, and the trusted-packaging ExecPlan each name the tests that hold those behaviours, so a later edit can tell which claim is pinned and where. Co-Authored-By: Claude Code <noreply@anthropic.com>
`crates_io_candidate_cli` reached 404 lines against the repository's 400-line module limit, and CodeScene reported the identity matcher over its complexity threshold. Move the `CandidateIssue`/`CandidateError` taxonomy into `crates_io_candidate_errors`, leaving each reader to raise a shared vocabulary without importing the command-line module that composes them. Extract the release-mode comparison from `_identity_matches` so the identity and tag comparisons are separately testable. Collapse the duplicated publishing/rehearsal test helpers into one builder that derives the mode from the expectation, removing the structural duplication CodeScene flagged. Co-Authored-By: Claude Code <noreply@anthropic.com>
`crates_io_candidate_cli` still averaged above CodeScene's mean-complexity threshold after the error vocabulary moved out. The archive and manifest readers are a cohesive tar/TOML boundary that the command-line module only composes, so move them into `crates_io_candidate_archive`. Both modules now score 10.0 in CodeScene's file review, and the packaging manifest is validated by a unit that can be tested without the CLI. Co-Authored-By: Claude Code <noreply@anthropic.com>
"Supplying neither mode, or both" pairs `neither` with `or`, which is ungrammatical. Say "no mode, or both modes" in the developers' guide and the trusted-packaging ExecPlan. Co-Authored-By: Claude Code <noreply@anthropic.com>
`archive_and_crate` and `read_packaged_manifest` are the module's public surface, so give each a structured docstring naming its parameters, return value, and the candidate rejections it raises. `pathlib` stays in the type-checking block: the module runs on the Python 3.14 baseline, where annotations are deferred, and Ruff's TC003 requires the import to be type-only because no runtime code uses it. Co-Authored-By: Claude Code <noreply@anthropic.com>
The metadata-mode tests and the check-mode tests were sharing one module. The base branch's observability work added metadata coverage to it, taking the file to 422 lines and tripping Pylint's C0302 cap at 400. Move the metadata tests into ``test_crates_io_candidate_metadata.py``, so each module states one command's contract and both stay under the cap. The Makefile recipe, the workflow-contract module list, and the developers' guide all name the new module, keeping the target's declared and executed module sets equal. Co-Authored-By: Claude Code <noreply@anthropic.com>
The offline candidate contract asserted only that both release-mode assignments appeared somewhere in the step's shell. A swapped mapping would have passed: a dry run would forward `--tag ""` and a publishing run would forward `--rehearsal`, failing only at runtime. Resolve each assignment from its own `case` arm line and compare the whole assignment, so the arm label and the mode it selects are bound together. The existing mutation case still replaces the rehearsal assignment and is still rejected. Co-Authored-By: Claude Code <noreply@anthropic.com>
CodeRabbit flagged three trivial style issues in the candidate boundary. The private helpers in the candidate CLI and the package contract test carried multi-paragraph docstrings with structured sections, which the project's pydoclint configuration exempts for one-line private helpers; their rationale now sits in a comment beside the code. The archive reader's two `isinstance` branches now use structural pattern matching, which the repository's Python typing rules prefer for branching over structured data. Error classification is unchanged: a non-string or path-shaped crate name still raises CRATE_BASENAME, a missing or non-mapping package table still raises PACKAGE_TABLE, and a mapping that names another package still raises MANIFEST_IDENTITY. Co-Authored-By: Claude Code <noreply@anthropic.com>
CodeRabbit noticed that the new archive adapter was missing from the release-publication inventory: `SOURCE_MODULES` drives both the `--doctest-modules` command line and `NETWORK_FREE_MODULES`, so the module's tar and TOML boundary was unchecked. List it in `SOURCE_MODULES`, `ADAPTER_MODULES`, and the Makefile recipe, and give `archive_and_crate` a doctest example so the collected doctest count is not vacuous. Widen the package contract's offline denylist to cover credential references, other fetch tools, and registry URLs anywhere in the step, not only in the tokenized `uv run` command. The assertion remains a denylist, not a proof, so the ADR and ExecPlan now describe it as a check on the step's shell text rather than a guarantee that the step reaches no registry endpoint. Co-Authored-By: Claude Code <noreply@anthropic.com>
The candidate error module was the one release-publication source the boundary contracts did not name, so a later edit that gave it file or network effects would have passed unnoticed. List it in `SOURCE_MODULES` and `POLICY_MODULES`, wire it into the `test-release-publication` doctest command, and give `CandidateError` an example so the collected doctest count stays non-zero. The module only formats classified issues, so the policy-side classification holds. Co-Authored-By: Claude Code <noreply@anthropic.com>
The ExecPlan's progress log stopped at the publisher telemetry work, so the offline candidate boundary and the interrupted-publication resume path that issue #1065 delivers were absent from the plan's authoritative record. Add one entry naming the release-mode contract, the archive and error modules and their registration, the offline step in the `package-crate` action, the resumed publication sequence, and the gate and review evidence for the head. The entry also restates that the finalization, inventory-verification, and consumer-smoke stages remain open, so the plan does not read as an end-to-end release path. Gates: check-fmt, markdownlint (with spelling), and nixie. Co-Authored-By: Claude Code <noreply@anthropic.com>
8cf0952 to
ddbb125
Compare
|
@coderabbitai please assess the implementation in this PR for completeness and correctness against the execplan:
Published head: ddbb125; comparison base: f066be4. This is a candidate-bound follow-up to your earlier assessment of head Acceptance evidence, local, sequential, one gate at a time, on the published head with a clean working tree:
Hosted evidence on the immediately preceding head Known limitations: no live OIDC exchange, registry upload, or release finalisation has been performed by this branch; the Assessment-ID: utp-1065-ddbb1258-20261104 Please echo the Assessment-ID and assessed base/head in your substantive reply. Identify remaining in-scope omissions or correctness defects and any access or inspection limitations. Assess the implementation, not just the plan text. |
|
Tip For best results, initiate chat on the files or code changes. Analysis chain
Implementation assessmentI inspected the current-head code, the exact GitHub base/head comparison, the relevant tests, and the ExecPlan requirements. The comparison reports 14 commits and 13 changed files. The publisher action and release workflow are inherited implementation, not changes introduced by this follow-up.
The rejection coverage includes ambiguous release modes, identity mismatches, path-shaped archive names, multiple archives, symlinked archives, digest mismatches, and packaged-manifest mismatches. Registry coverage retains conflict, yanked, malformed, unavailable, and downloaded-byte mismatch cases. Keep these completion boundaries explicitDo not describe the retry tests as a live interrupted publication. The tests supply recorded registry responses and local downloaded bytes. Separate contracts check the action sequence. They do not execute an upload and then interrupt a running GitHub Actions job. Keep the following wider-plan work open:
These gaps prevent full ExecPlan completion. They do not establish a missing requirement in this candidate-bound follow-up. Verification and inspection limits
Retain the scoped completion claim, not an end-to-end release-readiness claim. No formal PR review or approval action was requested or submitted. You are interacting with an AI system. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |

Summary
Completes the crates.io Trusted Publishing path by validating the packaged candidate before it leaves the build job and by making an interrupted publication converge on a rerun.
Closes #1065
scripts/crates_io_candidate_cli.pynow takes an explicit, mutually exclusive release mode.--tagkeeps the publishing contract, where the candidate must record exactly the tag being released.--rehearsalaccepts only a tagless candidate, so a pull-request lane can exercise the same boundary. Supplying no mode, or both modes, is a usage error.package-cratecomposite action runs that check offline, after packaging and before the artefact upload, selecting--rehearsalfor a dry run and--tagfor a publishing run. It reads only the local identity record and archive: no token, nocurl, no crates.io endpoint.No long-lived registry secret is introduced and no registry job gains GitHub release-write authority; the exchange job and its
contents: read/id-token: write/crates-ioenvironment contract are unchanged.Testing
make check-fmtmake lint-pythonmake typecheck-pythonmake markdownlint(includesmake spelling)make nixiemake test-workflow-contractsmake test-release-publicationmake test-documentation-contractsmake github-actions-lintmake doc-coveragemake testmake lintStack
Stacked on #1077 (
issue-1064-…), which targetsmain.References
🤖 Generated with Claude Code
Summary by Sourcery
Enforce crates.io candidate validation before artifact handoff and make rerun publication converge safely after an interrupted upload.
New Features:
Enhancements:
Documentation:
Tests: