Skip to content

feat(release): Trusted Publishing (OIDC) for crates.io (#96)#122

Merged
EffortlessSteven merged 2 commits into
mainfrom
feat/96-trusted-publishing
Apr 18, 2026
Merged

feat(release): Trusted Publishing (OIDC) for crates.io (#96)#122
EffortlessSteven merged 2 commits into
mainfrom
feat/96-trusted-publishing

Conversation

@EffortlessSteven

Copy link
Copy Markdown
Member

Summary

Switch the release workflow from a long-lived CARGO_REGISTRY_TOKEN
secret to Trusted Publishing via rust-lang/crates-io-auth-action@v1.
Short-lived tokens, scoped to this repo + release.yml + release
environment. Closes #96.

Shipper's auth layer (ops/auth/oidc.rs) already detects
ACTIONS_ID_TOKEN_REQUEST_URL + _TOKEN and returns
AuthType::TrustedPublishing — only the workflow side was missing.

Changes

  • .github/workflows/release.yml:
    • id-token: write at workflow level.
    • publish-crates-io, release-rehearse, release-resume: each runs
      rust-lang/crates-io-auth-action@v1 before invoking shipper, feeds
      steps.auth.outputs.token into CARGO_REGISTRY_TOKEN.
    • Fallback: steps.auth.outputs.token || secrets.CARGO_REGISTRY_TOKEN
      so incident response / bootstrap still works when OIDC can't mint.
    • release-rehearse marks the auth step continue-on-error: true
      (rehearse is best-effort and shouldn't block on a missing OIDC scope).
  • docs/how-to/run-in-github-actions.md: replace minimal stub with
    full recipe — one-time crates.io registration steps, environment scope
    guard, troubleshooting section (3 common failure modes).
  • docs/release-runbook.md: promote Trusted Publishing from
    "preferred" to primary auth path; keep token fallback documented.

Prerequisite (NOT code — one-time admin task)

Each of the 12 crates must be registered on crates.io as a Trusted
Publisher with:

  • Repository: EffortlessMetrics/shipper
  • Workflow: release.yml
  • Environment: release

Until registration is complete for all 12 crates, the fallback keeps
the train runnable via the secret. First release on the OIDC path
should be a rehearse run, then a real tag.

Test plan

  • release.yml parses as valid YAML.
  • Next workflow_dispatch mode=rehearse run should show the
    "Mint crates.io token" step succeed (or continue-on-error if the
    repo isn't yet registered).
  • First real publish via the OIDC path: observe that every
    per-crate cargo publish inside shipper publish picks up the
    minted token without reaching for the secret.

@coderabbitai

coderabbitai Bot commented Apr 17, 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 16 minutes and 18 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 16 minutes and 18 seconds.

⌛ 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: 1a94913e-ef97-48c8-96e4-9d672e966e35

📥 Commits

Reviewing files that changed from the base of the PR and between 39592e8 and d8ea9bf.

📒 Files selected for processing (3)
  • .github/workflows/release.yml
  • docs/how-to/run-in-github-actions.md
  • docs/release-runbook.md
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/96-trusted-publishing

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 updates the documentation to prioritize and detail the setup for Trusted Publishing (OIDC) on crates.io, including a new workflow example and troubleshooting guide. It also clarifies the authentication hierarchy in the release runbook. Feedback includes correcting non-existent GitHub Action versions in the documentation and adding reliability flags to the example shipper publish command to better demonstrate the tool's core features.

Comment on lines +117 to +118
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The action versions actions/checkout@v6 and dtolnay/rust-toolchain@stable appear to be incorrect. actions/checkout is currently at v4, and dtolnay/rust-toolchain typically uses @v1 with the toolchain specified in a with block. Using non-existent versions will cause the GitHub Action to fail.

Suggested change
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable

# Falls back to the long-lived secret if OIDC is unavailable
# (e.g. during incident response or the first bootstrap run).
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token || secrets.CARGO_REGISTRY_TOKEN }}
run: shipper publish --policy safe

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

For production releases, it is recommended to include the reliability flags used in the minimal workflow example (lines 58-60), such as --readiness-method both, --max-attempts, and --max-delay. This ensures the publish process is resilient to registry backpressure and transient network issues, which is a core value proposition of Shipper.

Suggested change
run: shipper publish --policy safe
run: |
shipper publish \
--policy safe \
--readiness-method both \
--max-attempts 12 \
--max-delay 15m

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98f487d971

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +236 to +238
- name: Mint crates.io token (Trusted Publishing)
id: auth
uses: rust-lang/crates-io-auth-action@v1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep publish job running when OIDC token mint fails

This step is documented as having a secret-token fallback, but without continue-on-error: true any crates.io OIDC exchange failure (missing/denied id-token, crates.io outage, or trusted-publisher mismatch) aborts the job before steps.auth.outputs.token || secrets.CARGO_REGISTRY_TOKEN is ever evaluated in later steps. That makes the advertised bootstrap/incident-response fallback path non-functional for publish-crates-io (and the same pattern appears in release-resume).

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Apr 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@EffortlessSteven EffortlessSteven force-pushed the feat/96-trusted-publishing branch from 98f487d to 3d13cd4 Compare April 17, 2026 12:55
@EffortlessSteven EffortlessSteven marked this pull request as draft April 17, 2026 13:20
@EffortlessSteven

Copy link
Copy Markdown
Member Author

Parking as draft per sequencing feedback: #96 belongs after #97 + #98 + #95 in the queue, not ahead. The trust-core + lifecycle pillars need to land before auth polish.

Content here is correct and ready — just not the next thing to ship. Will re-surface once #95 lands.

@EffortlessSteven EffortlessSteven force-pushed the feat/96-trusted-publishing branch from 3d13cd4 to 9d963a1 Compare April 18, 2026 06:33
@EffortlessSteven EffortlessSteven marked this pull request as ready for review April 18, 2026 06:33
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@EffortlessSteven

Copy link
Copy Markdown
Member Author

Rebased onto current main (now includes #97 stack + #98 stack merges). Ready for review/merge.

Note: this should land after #135 (the reframed #95 packaging unification). No hard conflict between them, but landing #96 first and then #95 would force another rebase on the release.yml workflow since #95 also touches it for the -p shipper build switch.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9d963a1bcf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

id: publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token || secrets.CARGO_REGISTRY_TOKEN }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use secret fallback when OIDC token lacks crate scope

This fallback expression only helps when the auth step returns no token, but Trusted Publishing can return a token that still fails on some crates (the docs added in this commit describe 401s for unregistered crates). In a partial-registration rollout, this line will always prefer the OIDC token and never use secrets.CARGO_REGISTRY_TOKEN, so shipper publish can fail mid-train even though the workflow advertises a bootstrap/incident fallback.

Useful? React with 👍 / 👎.

Comment on lines +420 to +423
- name: Mint crates.io token (Trusted Publishing)
id: auth
uses: rust-lang/crates-io-auth-action@v1
continue-on-error: true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bind rehearsal OIDC minting to the release environment

The new rehearsal auth step runs in a job without environment: release, but the same commit’s docs recommend configuring trusted publishers with the release environment as a scope guard. That mismatch means OIDC minting will keep failing even after setup is complete, and continue-on-error: true will silently mask it, so rehearsal cannot validate the real Trusted Publishing path before a production tag.

Useful? React with 👍 / 👎.

Wire the release workflow to mint a short-lived crates.io token per
run via `rust-lang/crates-io-auth-action@v1` instead of relying on a
long-lived `CARGO_REGISTRY_TOKEN` secret.

Shipper's auth layer already detects the OIDC env vars
(`ACTIONS_ID_TOKEN_REQUEST_URL` + `_TOKEN`) and returns
`AuthType::TrustedPublishing`; the workflow side is what was missing.

- release.yml:
  - add `id-token: write` at the workflow level
  - publish-crates-io, release-rehearse, and release-resume jobs each
    run `rust-lang/crates-io-auth-action@v1` before invoking shipper,
    and set `CARGO_REGISTRY_TOKEN` from `steps.auth.outputs.token`.
  - fallback to `secrets.CARGO_REGISTRY_TOKEN` via `||` so incident
    response / bootstrap still works when OIDC can't mint.
- docs/how-to/run-in-github-actions.md: replace the minimal OIDC stub
  with a full recipe — one-time crates.io registration steps,
  `environment: release` scope guard, and troubleshooting for the
  three common failure modes.
- docs/release-runbook.md: promote Trusted Publishing from "preferred"
  to the primary auth path; keep the token fallback documented.

Prereq (one-time, admin task, not code): register each published crate
as a trusted publisher on crates.io with this repo + release.yml +
`release` environment. Until that's done for all 12 crates, the
fallback keeps the train runnable.
Addresses #96 review concerns:

1. **Rehearsal scope guard.** `release-rehearse` now binds to
   `environment: release` so OIDC tokens it mints scope identically
   to production. Without this, rehearsal would only validate the
   mint mechanism while silently hiding scope-binding misconfiguration.

2. **Mixed-registration caveat.** Release workflow comment + how-to
   doc now explicitly require all crates to be registered as trusted
   publishers before enabling OIDC. Shipper's `preflight --policy safe`
   ownership check surfaces scope mismatches for existing crates;
   first-publish of unregistered new crates depends on operator
   discipline (documented).
@EffortlessSteven EffortlessSteven force-pushed the feat/96-trusted-publishing branch from 9d963a1 to d8ea9bf Compare April 18, 2026 07:15
@EffortlessSteven

Copy link
Copy Markdown
Member Author

Addressed the two review concerns:

  1. Rehearsal scope guard: release-rehearse now binds to environment: release (same as production). A rehearsal that successfully mints proves the scope wiring; a silent scope misconfig can no longer hide behind rehearsal.

  2. Mixed-registration fallback: documented explicitly that all crates must be registered as trusted publishers before enabling OIDC. Shipper's preflight --policy safe surfaces scope mismatches for existing crates (via ownership checks); new-crate first-publishes depend on operator discipline, now loudly documented in the how-to with the rationale.

--strict-ownership would break first-publishes of brand-new crates (no owner record yet), so the gate stays at best-effort ownership + strong docs.

Rebased on latest main. CI should re-run.

EffortlessSteven added a commit that referenced this pull request Apr 18, 2026
Three-column reconciliation of what's actually merged vs each issue's
acceptance checklist. Single source of truth for what closes each
pillar issue.

**Findings:**

- **#90 Recover** — honestly closable. Code side is done (#124 + #130);
  operator-side real rehearsal is an ops action, not a code gap.

- **#97 Prove tier 2** — 85% done. Rehearsal + visibility + hard gate +
  plan_id binding all landed (#127 + #133). Missing: install/smoke
  check (cargo install against the rehearsal registry / consumer
  build). One narrow follow-up PR closes it.

- **#98 Remediate** — 60% done. Receipt schema + plan-yank (from-receipt)
  + yank primitive + fix-forward planning all landed (#121 + #132 +
  #134). Missing: plan-yank's --starting-crate graph mode, plan
  execution for yank + fix-forward. Two narrow follow-ups.

Also captures the two review concerns on #122 (Trusted Publishing)
that were addressed in a follow-up commit to that PR.

Recommended next merge order and follow-up PRs spelled out at bottom.
EffortlessSteven added a commit that referenced this pull request Apr 18, 2026
Three-column reconciliation of what's actually merged vs each issue's
acceptance checklist. Single source of truth for what closes each
pillar issue.

**Findings:**

- **#90 Recover** — honestly closable. Code side is done (#124 + #130);
  operator-side real rehearsal is an ops action, not a code gap.

- **#97 Prove tier 2** — 85% done. Rehearsal + visibility + hard gate +
  plan_id binding all landed (#127 + #133). Missing: install/smoke
  check (cargo install against the rehearsal registry / consumer
  build). One narrow follow-up PR closes it.

- **#98 Remediate** — 60% done. Receipt schema + plan-yank (from-receipt)
  + yank primitive + fix-forward planning all landed (#121 + #132 +
  #134). Missing: plan-yank's --starting-crate graph mode, plan
  execution for yank + fix-forward. Two narrow follow-ups.

Also captures the two review concerns on #122 (Trusted Publishing)
that were addressed in a follow-up commit to that PR.

Recommended next merge order and follow-up PRs spelled out at bottom.
@EffortlessSteven EffortlessSteven merged commit 157dbfb into main Apr 18, 2026
19 checks passed
@EffortlessSteven EffortlessSteven deleted the feat/96-trusted-publishing branch April 18, 2026 07:45
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.

Trusted Publishing first-class: OIDC-based crates.io publishing in CI

1 participant