Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Connect publication

Publication starts from clean merged source under your repository’s release policy. Its preparation step is automatic execution, not another human version approval gate.

The standard reusable flow is folo-rs/cargo-release-plan-action/.github/workflows/release.yml. Select a tested published action release and replace ACTION_REVISION with the same verified immutable commit used for release checks. Keep registry, GitHub reconciliation, platform batches and reporting in the same workflow run. Tag and release events created using the workflow’s GITHUB_TOKEN do not trigger downstream workflows.

Add the release caller

For the running example, use .github/workflows/release.yml:

name: Release

on:
  push:
    branches: [main]
  workflow_dispatch:
    inputs:
      source:
        description: Optional full original publication-source commit for recovery
        type: string
        default: ''
      verify-publishing-identity:
        description: Verify OIDC exchange and revocation without publication
        type: boolean
        default: false

permissions: {}

jobs:
  release:
    if: github.repository == 'example/widgets' && !inputs.verify-publishing-identity
    permissions:
      contents: write
      actions: read
      id-token: write
      issues: write
    uses: folo-rs/cargo-release-plan-action/.github/workflows/release.yml@ACTION_REVISION
    with:
      source: ${{ inputs.source }}

  identity:
    if: github.repository == 'example/widgets' && github.event_name == 'workflow_dispatch' && inputs.verify-publishing-identity
    permissions:
      contents: read
      actions: read
      id-token: write
    uses: folo-rs/cargo-release-plan-action/.github/workflows/identity-probe.yml@ACTION_REVISION

Replace example/widgets and the example main branch with the configuration for your repository. The shared flow also validates the destination and release branch before writes. The caller grants the scopes the flow needs; individual jobs narrow them.

Omitted inputs use the shared workflow defaults. For a protected environment, add the same setting to both jobs and register that environment with crates.io:

with:
  publishing-environment: release

For path installation, add install-method: path and select the tool checkout with source-path. Released installation does not use source-path.

An empty source uses the invocation’s commit. For explicit recovery, supply the full original source SHA; the tool still verifies its release-branch membership. This is a new invocation with new evidence, never an automatic fallback when an old artifact is missing. The source override selects the publication source only. The controller is the cargo-release-plan executable run by the workflow. In source-install mode, source-path selects its source from the invocation checkout, so recovering old publication source does not rebuild an old controller.

Establish publishing identity

Every package must already exist on crates.io before automatic publication. Complete first publication before a new package’s first merge.

Register each package’s crates.io Trusted Publisher with:

  • Your GitHub repository owner and repository name.
  • Your calling entry workflow filename, including when it calls a reusable workflow from the action repository.
  • The publishing environment, if you use a protected GitHub environment.

The called action repository is not the publisher identity. The job exchanging OIDC credentials must use the environment registered for that caller. See GitHub’s reusable-workflow OIDC documentation.

Use phase-specific permissions. Registry publication needs source read access and id-token: write; GitHub tag, release and asset operations need repository content writes; failure-issue reporting needs issue writes. Grant only the scopes required by each job, including any read access the selected workflow needs for artifact transport and action-revision identification.

Do not provision a registry PAT or stored-token fallback. The application acquires short-lived credentials through OIDC for Cargo’s initial credential preflight and independently for each upload after verification. Preflight credentials are not reused for uploads. Cleanup attempts to revoke every issued credential and reports failures. Package verification shares the publication job’s trust boundary, including its job-level OIDC identity. Run only reviewed repository code and trusted build dependencies in that job; credential handling is not a sandbox against code running under the same account.

Verify identity without publishing

The permanent caller above includes a mutually exclusive identity-only mode. Dispatch it through the same registered entry workflow filename:

gh workflow run release.yml --repo example/widgets --ref main `
    --field verify-publishing-identity=true

Run this before enabling routine publication and whenever the registered caller or environment changes. The reusable probe checks OIDC exchange and revocation without a Cargo workspace or publication; it has no working-directory or config input. It uses the same action commit and environment as publication. Do not move the probe to a differently named caller or replace the production file to run it.

Its CLI operation is:

cargo release-plan check-publishing-identity

Run this in the authorized GitHub Actions OIDC context. It exchanges and immediately revokes a temporary credential, uploading nothing. Success exercises the caller identity path, not every package’s Trusted Publisher grant; verify package administration separately.

Capture the merged source

These commands demonstrate the CLI boundaries independently of workflow filenames. Run preparation from a clean checkout of the selected merged commit:

$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $true
$Source = (git rev-parse HEAD).Trim()
$Work = ".release-plan-work"
$Publication = Join-Path $Work "publication.json"
$Outcomes = Join-Path $Work "outcomes"
cargo release-plan prepare-publish --source $Source `
    --config (Join-Path ".cargo" "release_plan.toml") --output $Publication

$Source must be the full commit ID. Preparation verifies HEAD, clean source, tracked Cargo inputs and first-parent membership in the configured release branch. It fetches that branch to establish eligibility; it does not replace the selected source with the fetched tip. Preparation preserves the recorded lockfile without resolving dependencies. Cargo’s publication and native build commands use --locked.

Keep output in an ignored artifact directory or outside tracked source. Preparation writes no registry package, tag or release. Repeating identical intent may reuse the manifest; different intent needs a different destination.

In CI, prepare this manifest once and retain it for every subsequent phase and retry. A failed-job rerun downloads original intent rather than regenerating it from the current release-branch tip.

Inspect registry work without uploading

cargo release-plan publish registry --publication $Publication `
    --output (Join-Path $Work "registry-dry-run-1" "outcome.json") --dry-run

The dry run queries exact-version availability without exchanging publication credentials or uploading. Its outcome is never marked complete, even if every requested version already exists.

The following invocation performs live registry work and belongs only in the authorized publishing job, not tests or the local version-planning skill:

cargo release-plan publish registry --publication $Publication `
    --output (Join-Path $Outcomes "registry-attempt-1" "outcome.json")

Use a new outcome path on every attempt. Naming each live phase file outcome.json inside its own artifact subdirectory lets the final reporter discover it. Keep local dry-run and nonpublishing verification output separate from those publication outcomes. When the workspace is nested or the source checkout moved between jobs, --manifest-path must select the original publication-source workspace. Do not substitute a prospective planning workspace or later source commit.

The Cargo runtime requirement supports locked package verification and dependency-ordered workspace uploads. Cargo owns normalization and hashing of the package bytes it uploads. Neither a failed upload command nor an old successful outcome replaces a fresh exact-version observation.

Continue through GitHub and native jobs

After successful registry reconciliation, inspect GitHub work without writes:

cargo release-plan publish github --publication $Publication `
    --output (Join-Path $Work "github-dry-run-1" "outcome.json") `
    --batches (Join-Path $Work "batches-dry-run-1") --dry-run

A dry run is not completed delivery and cannot establish missing tags for native builds. The authorized GitHub job performs live reconciliation:

$Batches = Join-Path $Work "batches-attempt-1"
$GithubOutcome = Join-Path $Outcomes "github-attempt-1" "outcome.json"
cargo release-plan publish github --publication $Publication `
    --output $GithubOutcome --batches $Batches

The outcome supplies relative paths and batch_id values for frozen platform batches. Both the outcome path and batch directory must be new. For a nested workspace, pass --manifest-path for the original publication source, just as in registry publication.

Do not derive binary work from “packages uploaded in this attempt.” A registry no-op can still require a missing tag, release or checksum.

A failure to tag one release does not suppress valid work for another. Valid manifest-linked batches can continue after registry prerequisites succeed, while the reconciliation failure remains a workflow failure. Missing batch artifacts, invalid identities or cancellation do not authorize downstream work.

On the matching native runner, select the emitted batch for its target. This example assumes the outcome includes Windows x64 work:

Choose an absolute shared Cargo build-cache location outside the clean source, or beneath an ignored directory. This preserves source cleanliness in repositories that do not ignore Cargo’s default target directory:

$env:CARGO_TARGET_DIR = [IO.Path]::GetFullPath((Join-Path $Work "build-cache"))
$Routing = Get-Content $GithubOutcome -Raw | ConvertFrom-Json
$Selected = $Routing.batches | Where-Object target -eq "x86_64-pc-windows-msvc"
if ($null -eq $Selected) {
    throw "This outcome contains no Windows x64 batch."
}
$Batch = Join-Path $Batches $Selected.path
cargo release-plan publish binaries --publication $Publication --batch $Batch `
    --output (Join-Path $Outcomes "windows-attempt-1" "outcome.json") `
    --artifacts (Join-Path $Work "windows-artifacts-attempt-1")

The tool rechecks tag identities and assets, then builds each executable separately from its actual peeled tag commit. To stage the frozen batch without tag/release/asset queries or uploads, use --no-upload with separate destinations:

cargo release-plan publish binaries --publication $Publication --batch $Batch `
    --output (Join-Path $Work "windows-verification-1" "outcome.json") `
    --artifacts (Join-Path $Work "windows-verification-artifacts-1") --no-upload

This mode still verifies source and archives. It may fetch an unavailable source commit from the configured repository, including using repository-read authentication. It does not discover a batch, select substitute source or produce a completed publication outcome.

Report every attempt

The final reporting job downloads each phase’s artifact subdirectory, retaining its outcome.json, and supplies current job results as jobs.json. The fixed keys are prepare, registry, github and binaries; values come from GitHub’s job results, including the binary matrix’s aggregate result:

{
  "prepare": "success",
  "registry": "success",
  "github": "failure",
  "binaries": "skipped"
}

This is a format example, not values to hardcode into a real report. Run the reporter even after failed phases:

cargo release-plan publish report --repository example/widgets `
    --publication $Publication --outcomes $Outcomes `
    --jobs (Join-Path $Work "jobs.json") `
    --output (Join-Path $Work "publication-report-attempt-1.md")

The command requires the GitHub run context, writes the Markdown report even for incomplete delivery, and exits nonzero when work remains. It creates or updates the run-qualified failure issue with exact operator recovery instructions. --no-issue disables issue writes but does not remove the run-context requirement or turn an incomplete report into success.

If the publication manifest is unavailable, omit --publication. The reporter still writes a report and failure issue; missing intent is always incomplete, never a successful empty release.

Outcomes carry optional github: {run_id, run_attempt} metadata when produced in GitHub Actions. Preserve it so the reporter can enforce outcome identity and freshness.

Prepare native build environments

Configure the subset your packages support from the supported native targets. The action revision owns the corresponding runner mapping.

Provide native libraries or other source-build prerequisites through the fixed optional .github/actions/release-plan-setup/action.yml. It prepares the environment in Cargo verification and binary-build jobs, not GitHub-only reconciliation jobs, and must not edit captured source.

The action’s controller installation, the invocation checkout and each tagged binary source are distinct. A historical tag need not contain the current controller or current publication configuration.

Queue and retain, without hiding failures

Serialize publication runs for the same repository, release branch and workspace with non-cancelling queued concurrency (queue: max). Disable matrix fail-fast so independent native targets can finish. Queue capacity and platform cancellation still need operational attention.

Choose artifact retention long enough for operator recovery. Preserve the manifest, frozen batches, every attempt outcome and diagnostics, including on failure. Use run/attempt-qualified artifact names from the selected action interface rather than overwriting intent.

Queuing publication does not prevent a newer version from merging. For superseded-version tag failures, follow the original-source manual-tag recovery.