Expand description
Rust ecosystem adapter: cargo-publish / cargo-publish-ci (crates.io) and
cargo-dist.
cargo-publish publishes a crate to crates.io via cargo publish.
cargo-dist plans and builds distributable binaries locally (dist), but
its upload is the CI release workflow — so its publish body is
AdapterError::Unsupported from this host rather than a fabricated receipt
for a build-only command. verify (for cargo-publish) reconciles against
crates.io through RegistryQuery via the
adapter’s default path.
§cargo-publish-ci — the crates.io publish runs in CI, not here
cargo-publish-ci is cargo-publish’s CI-delegated identity, for a repo
whose release model is “push the version tag; a tag-triggered workflow runs
cargo publish with the repo’s registry secret” (glasspad’s
publish-crates.yml, the common publish-from-CI-not-a-laptop pattern). Such a
repo deliberately forbids the local publish: the maintainer’s
~/.cargo/credentials.toml may be stale (403) or absent, and the CI token is
the source of truth — so an engine cut that ran cargo publish here would
either fail or race the workflow into a double-publish.
It differs from cargo-publish in exactly one respect: the publish. Its
dry_run and build run the identical local gates (cargo check, cargo package --no-verify), because those are read-only preflights whose whole value
is catching an unpublishable manifest before the irreversible tag push — and a
repo that publishes from CI still wants them. Its publish is
AdapterError::Unsupported, and it declares
is_ci_delegated, so the coordinator journals
target_delegated and skips it in publish-all (never publishing, never failing).
It does not own the GitHub Release (it uploads to crates.io), so a plan
carrying only this delegated identity still gets an engine-created Release.
The result is a cut whose terminal actionable phase is the tag push, followed by the mandatory verify barrier — which polls the crates.io index until CI’s publish is observed (see the coordinator’s delegated-verify wait). “Delegated” never means “assumed”: an unobserved target still fails the cut.
§One plan target = one publish unit (ADR-0004)
Each plan target publishes exactly its own package — one target ⇒ one
cargo publish -p <package>. The coordinator owns
all cross-target ordering: it cuts same-ecosystem targets in dependency order
(a dependency’s target before its dependents’), so the adapter never re-orders
or re-publishes another target’s crate. This removes the earlier
closure-per-target model, where two authorities (coordinator + adapter) each
computed overlapping publish orderings and the crates.io publish→index lag
between them could trigger a duplicate cargo publish of a shared dependency
→ a partial-publish trap.
A workspace whose crates depend on one another still cannot publish in one
shot: crates.io rejects a crate whose sibling dependency is not yet indexed
(no matching package named … found). So before publishing its own package,
the adapter discovers that package’s publishable intra-workspace dependencies
(read-only cargo metadata) and waits for each to be crates.io-index-visible
(polling the injected RegistryQuery, bounded by
a timeout) — the dependency’s own target, cut earlier, already published it, so
this only closes the index-lag window. A crate with no publishable workspace
dependencies publishes immediately with no wait.
§Deferred packaging for a =-pinned dependent (cargo-interleave, ADR-0002)
A dependent crate that pins its workspace dependency by exact version
(dep = "=X.Y.Z", the shape /shipshape-init emits) cannot be cargo packaged
before that dependency is published — not even with --no-verify. cargo package resolves the =-pinned dependency against the crates.io index while
preparing the upload (a published .crate cannot reference a path dep), and
that version only lands later, in publish-all. --no-verify skips the isolated
verify compile, but not this index resolution. So a strict build-all that
packaged every crate up front could never package such a dependent
(release-cut-build-phase-dep-ordering).
The fix scopes the ADR-0002 phase barrier narrowly for cargo. dry_run /
build read the workspace graph and probe the registry (see
unpublished_workspace_deps), then branch on whether the target depends on a
workspace crate not yet on the crates.io index:
- No unpublished workspace dep (a leaf, or a dependent whose workspace deps
are already published — a re-cut): fully packaged pre-publish —
cargo check(compile safety net) thencargo package --no-verify(produces the.crate, validating the manifest). It CAN be packaged:cargo packageresolves the dep against the index it is already on. - Depends on a not-yet-published workspace crate: its packaging is deferred
to
cargo publishin publish-all, which packages and publishes as one unit after the dependency is published and index-visible (buildruns only the index-independentcargo check, which resolves the sibling via itspath). This is the “build interleaves with publish” exception the coordinator relies on: the dependent’s package step is intrinsic to its dep-ordered publish, not a premature global-build step. The pre-publish compile safety net (cargo checkover every target) still runs as a global build-all barrier, so a compile error in the default host build fails before any irreversible publish.
The registry probe is fail-closed: a dep the registry cannot confirm as
published defers, so a registry outage never risks a build-all cargo package
that resolves a =-pinned dep against an index it cannot reach.
The consequence is a target model where each publishable crate is its own
declared target (which is what /shipshape-init emits). A multi-crate workspace
that wants every crate on crates.io declares every crate as a target; a target
whose package depends on a workspace crate that is not itself a declared
target — and whose required version is not already on the index — times out
waiting for that crate, the signal that it must be declared. (If that
dependency’s version happens to already be published, the wait clears and the
publish proceeds; the coverage check that would catch an under-declared plan up
front is tracked separately, not owned by the adapter.)
Structs§
- Cargo
Adapter - The rust release adapter, operating as
cargo-publish,cargo-publish-ci, orcargo-dist.