Skip to content

xtask: fix PUBLISH_CRATES order so compat shims publish after their owners#565

Merged
EffortlessSteven merged 2 commits into
mainfrom
xtask/fix-publish-order
May 11, 2026
Merged

xtask: fix PUBLISH_CRATES order so compat shims publish after their owners#565
EffortlessSteven merged 2 commits into
mainfrom
xtask/fix-publish-order

Conversation

@EffortlessSteven

Copy link
Copy Markdown
Member

Summary

v0.7.0 release blocker. The release workflow for tag `v0.7.0` failed on `cargo xtask publish` with:

```
error: failed to prepare local package for uploading
Caused by: failed to select a version for the requirement `uselesskey-core = "^0.7.0"`
candidate versions found which didn't match: 0.6.0, 0.5.1, ...
required by package `uselesskey-core-seed v0.7.0`
```

The v0.7.0 fold-into-`srp::` refactor turned the former `uselesskey-core-` and `uselesskey-core-x509*` shards into compatibility shims that depend on their owner crates. But `PUBLISH_CRATES` still listed them at the top under "Leaf crates", which walked them before `uselesskey-core` / `uselesskey-x509` could land on crates.io.

Inversions before this PR

Topo-sort over `cargo metadata` deps found 14 inversions:

  • 9 core shims (`core-seed`, `core-hash`, `core-id`, `core-cache`, `core-factory`, `core-negative-der`, `core-negative-pem`, `core-negative`, `core-sink`) listed before `uselesskey-core`
  • 5 x509 shims (`core-x509-spec`, `core-x509-derive`, `core-x509-chain-negative`, `core-x509-negative`, `core-x509`) listed before `uselesskey-x509`

Fix

Reorder `PUBLISH_CRATES` so:

  • core JWK shards publish first (the only true workspace-dep-free leaves)
  • `uselesskey-core` follows
  • the 9 core compat shims publish after `uselesskey-core`
  • mid-level fixture and token crates follow
  • `uselesskey-x509` publishes before its 5 x509 compat shims
  • facade `uselesskey` stays last

Validation

  • Topo-sort over `cargo metadata --no-deps` dependencies: 53 crates, 0 inversions (was 14)
  • `cargo check -p xtask` — passes

Recovery plan for v0.7.0

After this PR merges:

  1. `git tag -d v0.7.0 && git push --delete origin v0.7.0`
  2. Re-tag the new `main` HEAD (which includes this fix) as `v0.7.0`
  3. Push the new tag; `release.yml` runs end-to-end with correct ordering

Nothing v0.7.0 reached crates.io on the first attempt (verified: all crates still default to 0.6.0 on crates.io), so retag is safe.

Follow-up

`cargo xtask publish-check` did not catch this because its dry-run resolves workspace deps against local paths. A follow-up PR will add a topo-sort verification to publish-check so future ordering bugs fail loudly before tag push.

Test plan

  • CI green (fmt, check, clippy, test-compile, public-surface, docs-sync, publish-preflight, publish-check)

…wners

The v0.7.0 release workflow failed on `cargo xtask publish`:

  error: failed to prepare local package for uploading
  Caused by: failed to select a version for the requirement
             `uselesskey-core = "^0.7.0"`
    candidate versions found which didn't match: 0.6.0, 0.5.1, ...
    required by package `uselesskey-core-seed v0.7.0`

The v0.7.0 fold-into-`srp::*` refactor made the former core/x509 shards
into compatibility shims that depend on their owner crates. But
PUBLISH_CRATES still listed them at the top under "Leaf crates", which
walked them before `uselesskey-core` / `uselesskey-x509` could land on
crates.io.

Reorder the list so:

- core JWK shards publish first (the only true workspace-dep-free leaves)
- `uselesskey-core` follows
- the 9 core compat shims (seed, hash, id, cache, factory, negative-*,
  sink) publish after `uselesskey-core`
- mid-level fixture and token crates follow
- `uselesskey-x509` publishes before its 5 x509 compat shims (spec,
  derive, chain-negative, negative, x509)
- facade `uselesskey` stays last

Verified by topological sort over `cargo metadata` dependencies: 53
crates, 0 inversions (was 14).

publish-check did not catch this because its dry-run resolves workspace
deps against local paths, masking the crates.io-only failure. A
follow-up PR will add a topo-sort verification to publish-check.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@EffortlessSteven has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 40 minutes and 20 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 20a02558-d2a5-4020-946c-c56c5e9cb816

📥 Commits

Reviewing files that changed from the base of the PR and between 310a70f and 964a615.

📒 Files selected for processing (1)
  • xtask/src/main.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch xtask/fix-publish-order

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request reorders the PUBLISH_CRATES array in xtask/src/main.rs to organize crates by category and dependency order. Feedback suggests further adjustments to ensure compatibility shims, such as uselesskey-core-hmac-spec, are published after their respective owner crates to prevent resolution errors during release. There is also a suggestion to clarify the 'True leaf crates' comment where internal dependencies exist for JWK shims.

Comment thread xtask/src/main.rs
Comment on lines +843 to +846
// True leaf crates (no workspace deps)
"uselesskey-core-hmac-spec",
"uselesskey-jwk",
"uselesskey-core-kid",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The crate uselesskey-core-hmac-spec appears to be a compatibility shim for uselesskey-hmac (as indicated by the mapping in compatibility_shim_owner at line 3943). If it depends on its owner crate, it must be published after it to avoid resolution errors during release. Currently, it is listed at line 844, while uselesskey-hmac is at line 872. I suggest moving it after its owner.

Also, the comment // True leaf crates (no workspace deps) is technically inaccurate for the JWK shims (like uselesskey-core-kid) if they depend on uselesskey-jwk. I've suggested a re-grouping to clarify this.

Note: uselesskey-core-rustls-pki (line 898) also appears to be an inversion relative to its owner uselesskey-rustls (line 902), though it falls outside this diff hunk.

    // True leaf crates (no workspace deps)
    "uselesskey-jwk",
    // JWK compatibility shims (depend on uselesskey-jwk)
    "uselesskey-core-kid",

Comment thread xtask/src/main.rs
"uselesskey-rsa",
"uselesskey-ecdsa",
"uselesskey-ed25519",
"uselesskey-hmac",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Move the HMAC compatibility shim here to ensure it is published after its owner.

    "uselesskey-hmac",
    // HMAC compatibility shims (depend on uselesskey-hmac)
    "uselesskey-core-hmac-spec",

The previous fix to PUBLISH_CRATES moved uselesskey-core-seed from
index 0 to index 8, which broke `resolve_start_index_from_first_crate`
in CI. The test was asserting that the *specific name*
"uselesskey-core-seed" resolves to index 0; what it actually wants to
verify is that the *first crate in the list* resolves to index 0,
whatever its name happens to be.

Switch the test to use PUBLISH_CRATES[0] so the assertion is robust to
future reorderings.

`cargo test -p xtask` — 258 passed.
@EffortlessSteven EffortlessSteven merged commit 6e220bb into main May 11, 2026
6 checks passed
@EffortlessSteven EffortlessSteven deleted the xtask/fix-publish-order branch May 11, 2026 09:52
EffortlessSteven added a commit that referenced this pull request May 11, 2026
Both v0.7.0 publish attempts via `cargo xtask publish` failed:

1. First attempt (tag at #562 merge): `uselesskey-core-seed` packaged
   before `uselesskey-core` because PUBLISH_CRATES listed compat shims
   before owner crates. Fixed in #565.
2. Second attempt (tag at #565 merge): `uselesskey-core` packaged but
   failed because its dev-dep `uselesskey-test-support` (workspace
   entry has `version = "0.7.0"`) is not in PUBLISH_CRATES at all.

Both failures are the same class of bug: a hand-maintained linear
PUBLISH_CRATES order that drifts out of sync with cargo metadata when
deps move. The user built `shipper` for exactly this — it computes a
deterministic DAG from cargo metadata, reconciles against registry
truth, retries transient errors, and persists state under `.shipper/`
so interrupted runs can resume.

Switch both `release.yml` and `publish-retry.yml` to:

- `cargo install shipper --locked --version "0.3.0-rc.2"`
- `shipper plan` (emit JSON plan into `.shipper/`)
- `shipper preflight --policy safe`
- `shipper publish --policy safe --readiness-method both --max-attempts 12 --max-delay 15m`
- Upload `.shipper/` state as artifacts (plan + final)

`publish-retry.yml` adds a `shipper resume --resume-from <crate>` path
when `inputs.resume_from` is set, and uses `shipper plan --format text`
as the dry-run preview.

Token continues to come from `secrets.CARGO_REGISTRY_TOKEN`. OIDC via
`crates-io-auth-action` is a separate follow-up.

## Recovery for the in-flight v0.7.0

3 crates are at 0.7.0 on crates.io (`uselesskey-core-hmac-spec`,
`uselesskey-jwk`, `uselesskey-core-jwk`); 50 remain at 0.6.0. After
this PR merges, dispatch `Publish Retry` on `v0.7.0` with `dry_run:
true` to preview, then dispatch again non-dry-run. Shipper reconciles
against the registry before any cargo activity, so the 3 already-
published crates will be skipped.

## Future work

- Adopt `rust-lang/crates-io-auth-action` OIDC token flow.
- Drop the hand-maintained `PUBLISH_CRATES` constant in `xtask/src/main.rs`
  once shipper handles all paths.
EffortlessSteven added a commit that referenced this pull request May 11, 2026
Shipper 0.3.0-rc.2 emits the publish plan in alphabetical-looking order
rather than topological order (EffortlessMetrics/shipper#173). With
\`uselesskey-aws-lc-rs\` at position 1 and \`uselesskey-core\` at #2,
shipper's linear walk fails immediately because cargo publish of
aws-lc-rs requires uselesskey-ecdsa (#22) and other deps to already be
on crates.io.

The owner of shipper will fix this in the next patch. Until then,
revert both \`release.yml\` and \`publish-retry.yml\` to use
\`cargo xtask publish\`. The PUBLISH_CRATES order is correct after #565,
and the test-helper dev-dep fix in #569 removes the cargo verification
gap that the original cargo xtask path also tripped over.

Keep \`.shipper/\` in \`.gitignore\` and the existing \`Quality gate\` /
\`Publish preflight\` / \`Publish dry-run\` steps in preflight job —
those are unaffected by the shipper revert and still useful.

After this PR merges, retag v0.7.0 on the new main HEAD and let
\`release.yml\` run end-to-end. shipper#173 stays open for the next
release cycle.
EffortlessSteven added a commit that referenced this pull request May 11, 2026
…ing) (#572)

The v0.7.0 release lane hit a publish failure because PUBLISH_CRATES had
compatibility shims listed before their owners: uselesskey-core-seed (a
shim that depends on uselesskey-core) was at index 0 while
uselesskey-core was at index 21. cargo xtask publish walks the list in
order, so it tried to publish uselesskey-core-seed v0.7.0 first and
cargo publish failed because uselesskey-core ^0.7.0 was not on
crates.io yet.

PR #565 reordered PUBLISH_CRATES to put owners before shims, but the
existing cargo xtask publish-check did not catch the bug locally because
its dry-run mode (cargo package --no-verify) resolves workspace deps
against local paths, not crates.io. The bug was only visible against the
live registry.

This adds a verify_publish_order_is_topological() function that uses
cargo metadata to walk every (crate, workspace-dep) pair in
PUBLISH_CRATES and asserts the dep is listed earlier. publish_check now
calls it before any dry-runs so a future reorder fails the local gate
before it can break the live publish. A matching unit test
(publish_order_is_topological) keeps the current ordering honest.

0 inversions exist today; the guard is a no-op against main HEAD.
EffortlessSteven added a commit that referenced this pull request May 12, 2026
…::spec (#595)

* refactor(hmac): fold core-hmac-spec into uselesskey-hmac::srp::spec

Inverts the dep direction between `uselesskey-hmac` and
`uselesskey-core-hmac-spec`. `HmacSpec`, its impl block, and its inline
tests now live in `crates/uselesskey-hmac/src/srp/spec.rs`;
`uselesskey-core-hmac-spec` becomes a thin re-export shim re-exporting
`uselesskey_hmac::srp::spec::*` to preserve the v0.7.x public-internal
surface for downstream pinners.

This matches the v0.7.0 fold pattern established for `core-cache`,
`core-id`, `core-hash`, `core-factory`, `token-spec`, etc. (#565-#570
and follow-ups) where the owner fixture crate took canonical ownership
and the previously-published microcrate became a compatibility shim.

Mechanical changes:

- New `crates/uselesskey-hmac/src/srp/{mod.rs,spec.rs}` carry the
  `HmacSpec` enum + helpers + inline `#[cfg(test)] mod tests` block.
- `crates/uselesskey-hmac/src/spec.rs` and `lib.rs` now re-export from
  `srp::spec`; the public `uselesskey_hmac::HmacSpec` path is unchanged.
- `uselesskey-hmac/Cargo.toml` drops the dep on
  `uselesskey-core-hmac-spec`.
- `uselesskey-core-hmac-spec/src/lib.rs` is now `pub use
  uselesskey_hmac::srp::spec::*;`. Its Cargo.toml adds
  `uselesskey-hmac.workspace = true` and updates the description.
- `xtask::PUBLISH_CRATES` and `xtask::plan::dependents()` move
  `uselesskey-core-hmac-spec` to the post-`uselesskey-hmac` position,
  matching `uselesskey-token-spec` after the token fold.
- `docs/explanation/architecture.md`, `docs/metadata/workspace-docs.json`,
  and the regenerated `docs/reference/support-matrix.md` describe the
  crate as a compatibility shim.

The shim's integration / mutant_killers / prop / snapshot tests are
unchanged and continue to pass via the new re-export. `stable_bytes()`
values, JOSE alg names, byte lengths, and Debug output are identical
to v0.7.1; no derivation-stability impact.

This is PR-1 of the v0.8.0 SRP collapse lane. The shim crate stays
published for v0.7.x compat and is scheduled for removal in PR-4.

* refactor(hmac): bump uselesskey-hmac and core-hmac-spec to 0.7.2

The fold introduces a new public `uselesskey_hmac::srp::spec` module
which the `uselesskey-core-hmac-spec` shim now re-exports. The
published v0.7.1 of `uselesskey-hmac` does not have this path, so
`cargo publish --dry-run -p uselesskey-core-hmac-spec` would resolve
its dep against registry v0.7.1 and fail with `E0433: cannot find
'srp' in 'uselesskey_hmac'`.

Bump both crates to v0.7.2 so the live publish lane resolves the shim
against the new (v0.7.2) `uselesskey-hmac` that ships in the same wave.
Downstream consumers (`uselesskey`, `uselesskey-jsonwebtoken`,
`uselesskey-jose-openid`, `uselesskey-rustcrypto`, `uselesskey-cli`,
`uselesskey-interop-tests`) keep their existing `version = "0.7.1"`
constraint — cargo treats it as `^0.7.1`, which accepts v0.7.2.

`publish-check` once this PR lands: the v0.7.2 publish of
`uselesskey-hmac` precedes the v0.7.2 publish of
`uselesskey-core-hmac-spec` (already enforced by PUBLISH_CRATES
topological order), so the shim's dry-run finds the new `srp::spec`
path on the registry.
EffortlessSteven added a commit that referenced this pull request May 12, 2026
…::srp::pki (#598)

* refactor(rustls): fold core-rustls-pki into uselesskey-rustls::srp::pki

Inverts the dep direction between `uselesskey-rustls` and
`uselesskey-core-rustls-pki`. The `RustlsPrivateKeyExt`,
`RustlsCertExt`, and `RustlsChainExt` traits and their impls for
RSA, ECDSA, Ed25519, and X.509 fixture types now live in
`crates/uselesskey-rustls/src/srp/pki.rs`;
`uselesskey-core-rustls-pki` becomes a thin re-export shim
re-exporting `uselesskey_rustls::srp::pki::*` to preserve the
v0.7.x public-internal surface for downstream pinners.

This matches the v0.7.0 fold pattern established for `core-cache`,
`core-id`, `core-hash`, `core-factory`, `token-spec`, etc. (#565-#570
and follow-ups) and the v0.8.0 hmac-spec fold (#595) where the owner
crate took canonical ownership and the previously-published microcrate
became a compatibility shim.

Mechanical changes:

- New `crates/uselesskey-rustls/src/srp/{mod.rs,pki.rs}` carry the
  three extension traits, all impl blocks, and the inline
  `#[cfg(test)] mod tests` blocks.
- `crates/uselesskey-rustls/src/lib.rs` declares `pub mod srp` (doc-
  hidden) and re-exports the pki traits at the top level. The public
  `uselesskey_rustls::{RustlsPrivateKeyExt, RustlsCertExt,
  RustlsChainExt}` path is unchanged.
- `uselesskey-rustls/Cargo.toml` drops the dep on
  `uselesskey-core-rustls-pki` and gains a direct `rustls-pki-types`
  dep; per-key-type feature flags drop the
  `uselesskey-core-rustls-pki/*` re-export legs.
- `uselesskey-core-rustls-pki/src/lib.rs` is now a thin shim:
  `pub use uselesskey_rustls::srp::pki::*`. Its Cargo.toml drops the
  direct rustls-pki-types and key-type deps, adds
  `uselesskey-rustls.workspace = true`, and routes its features to
  `uselesskey-rustls/*`.
- The shim's `tests/{integration,deterministic,snapshots_rustls_pki}.rs`
  and snapshot files move under `crates/uselesskey-rustls/tests/`.
  Snapshot file names and contents are preserved (same deterministic
  seed `uselesskey-rustls-pki-snap-seed-v1`); only the Rust import
  paths are rewritten from `uselesskey_core_rustls_pki::*` to
  `uselesskey_rustls::*`.
- `xtask::PUBLISH_CRATES` moves `uselesskey-core-rustls-pki` to a
  post-`uselesskey-rustls` slot under a "rustls compatibility shim"
  comment, matching `uselesskey-core-hmac-spec` after the hmac fold.
- `xtask::plan::dependents()` makes `uselesskey-core-rustls-pki` a
  leaf and adds it as a `uselesskey-rustls` dependent (so a rustls
  source change still triggers a shim rebuild).
- `docs/explanation/architecture.md`,
  `docs/metadata/workspace-docs.json`, and
  `docs/reference/support-matrix.md` describe the crate as a
  compatibility shim.

Both `uselesskey-rustls` and `uselesskey-core-rustls-pki` bump to
0.7.2 so the shim's re-export of `uselesskey_rustls::srp::pki`
resolves against a registry version that ships the new path:
`cargo publish --dry-run -p uselesskey-core-rustls-pki` would otherwise
resolve against the published v0.7.1 of `uselesskey-rustls`, which
does not have `srp::pki`, and fail with `E0432: unresolved imports`.

Downstream consumers (`uselesskey`, `uselesskey-bdd-steps`,
`uselesskey-interop-tests`) keep their existing `version = "0.7.1"`
constraint — cargo treats it as `^0.7.1`, which accepts v0.7.2.

This is PR-2 of the v0.8.0 SRP collapse lane. The shim crate stays
published for v0.7.x compat and is scheduled for removal in a later
v0.8.0 PR.

* docs(rustls): bump version snippets to 0.7.2 for the rustls fold

The `uselesskey-rustls` v0.7.2 bump (PR-2 of the v0.8.0 SRP collapse
lane) outpaces the workspace-wide `release_version: "0.7.1"` field
in `docs/metadata/workspace-docs.json`, which docs-sync uses to render
all dependency snippets uniformly. The preflight `doc-versions` lane
checks each rendered snippet against the per-crate
`Cargo.toml` version, so a uniform `0.7.1` snippet for
`uselesskey-rustls` would fail against its bumped manifest.

Add an optional `version` override to `SnippetDependency`, route it
through `render_single_dependency_snippet`, and set the override to
`"0.7.2"` on the `uselesskey-rustls` snippet entry. The other deps
in the same snippets continue to render against the workspace-wide
`release_version` field. Regenerate the README and how-to dependency
blocks accordingly.

This same pattern can be reused on subsequent SRP-fold PRs where a
single fold-target crate bumps ahead of the rest of the workspace.

* xtask(docs-sync): add per-dep version override test

Adds a unit test exercising the new optional `version` field on
`SnippetDependency`, locking in the SRP-fold pattern where a single
crate has been bumped ahead of the workspace-wide `release_version`
(e.g. v0.8.0 SRP folds where one crate goes to 0.7.2 while the rest
of the workspace stays on 0.7.1) so future regressions on the
override path are caught in `cargo test -p xtask`.

Also fixes the existing snippet-rendering test to provide the new
`version: None` field after the struct literal added the field.
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