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

Partial-publication and recovery runbook

This runbook covers an interrupted multi-crate publication. A crates.io upload is irreversible: a version can be yanked but cannot be deleted or replaced with different bytes. Recovery therefore resumes forward from verified artifacts; it never tries to overwrite an accepted version.

Preconditions

  • Freeze merges and deployments that consume the affected prerelease.
  • Preserve the release workflow run, tag, commit, checksums.txt, attestation, SBOM, audit output and every .crate archive.
  • Revoke a registry token immediately if credential compromise is suspected.
  • Assign an incident owner and record times, commands, registry responses and affected package versions.

Name ownership and first-publication credentials

The machine-readable policy in .github/crates-ownership-policy.json separates the expected crates.io owner (venelouis) from the GitHub identity trusted to run the release (Rullst/Rullst). The bootstrap allowlist is not an ownership claim: it is the reviewed set of names that may still return 404 before their first publication.

.github/check-crates-ownership.sh runs once in the verification job and again immediately before publication. Every registered package must include venelouis among its owners. Every unregistered package must be present in the bootstrap allowlist. A name registered by another owner, an unexpected missing name, an API error, or a malformed policy stops the release.

The following one-time bootstrap procedure was completed before the first v12 RC and again for the four new v13 names (see below). All twenty package names are now registered, so future releases must use Trusted Publishing and must not recreate a bootstrap secret unless a new, reviewed package name is introduced:

Historical procedure: the current release.yml has removed this token fallback and refuses every unregistered package, including on prerelease tags. Adding a name to the allowlist does not enable publication. Do not set the old secret and expect the steps below to work on the current workflow. A new name needs a reviewed change that restores the fallback, merged before the release admission, as PR #452 did.

  1. Protect the GitHub crates-io environment with required review and tag deployment rules.
  2. Configure Trusted Publishing for every already-registered package using GitHub owner Rullst, repository Rullst, workflow release.yml, and environment crates-io.
  3. Create a shortest-lived crates.io token with the publish-new endpoint scope. Restrict its crate-name scope to the reviewed bootstrap names if the crates.io UI permits that combination. It does not need publish-update: registered packages are published with the OIDC credential.
  4. Store it only as the CRATES_IO_BOOTSTRAP_TOKEN secret in the protected crates-io environment. Never put it in repository secrets, logs, command history, documentation, or a local Cargo credentials file.
  5. Start the tag workflow only after reviewing the generated ownership evidence. The publish loop selects the bootstrap token only for packages classified as unregistered-reviewed-bootstrap; registered packages use the short-lived Trusted Publishing token.
  6. After all new packages are indexed, configure the same Trusted Publisher for each of them, enable crates.io’s trusted-publishing-only protection, revoke the bootstrap token, delete the GitHub environment secret, and remove the names from the bootstrap allowlist in a reviewed change.

Trusted Publishing cannot be configured before a crate’s first release. The one-time token is therefore an explicit, bounded exception, not a permanent fallback. See the official crates.io Trusted Publishing announcement and Cargo publishing rules.

v13 package registration

rullst-privacy, rullst-supervision, rullst-media and rullst-labs were first published in 13.0.0-alpha.1 on October 11, 2026, from release run 38151068563. The crates.io documentation still requires an API token for a first publication.

The first attempt (run 38121092958) refused before uploading anything, because the publish job had no first-publication path. Registering the names outside the workflow was not possible: rullst-media depends on rullst-core at the same prerelease version. PR #452 restored the fallback above for that release:

  • The maintainer created a short-lived publish-new token and stored it only as the crates-io environment secret.
  • All twenty archives matched the release checksums on crates.io.

A reviewed change after that release reverted the fallback and emptied the allowlist. The maintainer then completed step 6 for the four crates:

  • set the Trusted Publisher to Rullst/Rullst, release.yml, environment crates-io;
  • enable trusted-publishing-only updates;
  • revoke the token and delete the environment secret.

The next release’s ownership check verifies that venelouis owns all twenty names.

Lesson: check for unregistered names before the admission. The release verification job accepted reviewed bootstrap names, so the refusal surfaced only in the publish job, two hours into the run.

  • .github/release-preflight.py now refuses an unregistered name before the admission is dispatched.
  • The verification job refuses it before packaging.

A change that restores the fallback must also remove that early refusal.

Determine the exact state

For each package in .github/release-order.json:

  1. query https://crates.io/api/v1/crates/<crate>/<version>;
  2. classify it as not indexed, indexed with the expected checksum, or indexed with an unexpected checksum;
  3. compare the registry checksum to the retained verified archive;
  4. verify that every already-published internal dependency precedes its dependent in the release order.

Do not infer success only from the exit status of cargo publish. Registry indexing is asynchronous, so poll with a bounded timeout. The release workflow performs this check after each package.

Recovery decisions

Nothing was accepted

Correct the failure on a new commit. If the tag or package content must change, use a new prerelease version such as 13.0.0-rc.2. Never move a published or publicly attested tag to unrelated content.

A prefix was accepted with matching checksums

Keep the accepted packages. Re-run the release workflow from the same immutable tag and verified artifact set. Its resume logic skips only versions whose crates.io checksum equals the retained archive and continues at the first missing package.

If source, manifest or artifact content needs any change, bump all publishable packages and internal requirements atomically to a new prerelease. Do not mix rebuilt bytes into the interrupted version.

A checksum is unexpected

Stop immediately. Do not publish dependents. Preserve evidence, revoke credentials, contact crates.io support and treat the event as a potential supply-chain incident. Yank the affected version when that reduces user risk, but remember that yanking does not remove downloaded bytes.

A dependency never becomes index-visible

Stop before its dependents. Retain the successful upload response and poll the API/index within the bounded operational window. If crates.io reports an incident, wait for service recovery; do not reorder the DAG or weaken checksum verification.

Consumer mitigation

  • Yank a broken or unsafe prerelease and publish a corrected new prerelease.
  • State exactly which packages/versions were accepted, which were yanked and which replacement users should select.
  • Never describe yanking as deletion or as proof that no consumer downloaded the artifact.
  • For a stable vulnerability, follow the advisory SLA and coordinated disclosure policy in Security advisory exceptions.

Completion evidence

Recovery is complete only when:

  • every version in the tagged release inventory is indexed with its retained checksum (16 packages for v12; 20 in the current v13 candidate);
  • a clean consumer resolves only registry packages and compiles;
  • documentation/index pages are reachable;
  • the incident timeline and any yanks/replacements are recorded;
  • temporary credentials are revoked and Trusted Publishing is restored;
  • the next release includes a regression for the initiating failure.

This process is package recovery, not application rollback. Database migrations, traffic rollback, secrets and deployed binaries require a separate application-specific operational plan.