crate-seq-core
crate-seq-core is the orchestration crate — it wires together every other crate in the workspace to implement the init, check, and publish workflows. It owns token resolution, pre-publish validation, topological workspace ordering, and the full publish pipeline (dry-run and live).
Module map
| Module | Purpose |
|---|---|
lib.rs |
Crate root — re-exports, module declarations |
workspace.rs |
Workspace member discovery from Cargo.toml |
auth.rs |
Three-tier crates.io token resolution |
init.rs |
init command orchestration |
check.rs |
Ledger-vs-registry diff reporting |
validate.rs |
Pre-publish path-dep and cargo check guards |
topo.rs |
Topological sort of workspace members |
pipeline/mod.rs |
Pipeline module root |
pipeline/source.rs |
Source resolution (git tag or snapshot) |
pipeline/dry_run.rs |
Dry-run publish pipeline |
pipeline/execute.rs |
Live publish pipeline |
pipeline/workspace_publish.rs |
Workspace-ordered multi-crate publish |
workspace.rs — member discovery
Discovers workspace members from a root Cargo.toml. Supports both multi-crate workspaces and single-crate repos.
Key types
WorkspaceMember— a discovered crate withname: String,manifest_path: PathBuf, andcrate_dir: PathBuf
Public API
discover_members(workspace_root: &Path) -> Result<Vec<WorkspaceMember>, Error>Reads the root
Cargo.toml. If a[workspace].membersarray exists, expands each entry (including/*globs) and reads each member'sCargo.tomlto extract[package].name. For single-crate (non-workspace) manifests, returns a single member representing the root crate.
Error cases
Error::Io— file not found or unreadableError::Toml— invalid TOML in a manifestError::MissingPackageName— memberCargo.tomlhas no[package].name
auth.rs — token resolution
Implements a three-tier strategy for resolving a crates.io API token, mirroring how CI and local development environments typically manage secrets.
Resolution order
- Tier 1 — CLI argument (
--token <TOKEN>): highest priority, for scripting and CI - Tier 2a — environment variable (
token_envin.crate-seq.toml): reads the named env var - Tier 2b — shell command (
token_cmdin.crate-seq.toml): runs the command string insh -c, captures trimmed stdout. Only used iftoken_envis not set - Tier 3 — Cargo credentials (
~/.cargo/credentials.toml): reads[registry].tokenfrom the standard cargo credentials file
Key types
TokenError— enum with variants:EnvVarEmpty { var }— env var is set but emptyCmdFailed { reason }— shell command failed or produced empty outputCargoCredentials(io::Error)— credentials file exists but is unreadable/invalidNotFound { tried }— all tiers exhausted
Public API
resolve_token(cli_token: Option<&str>, ledger_auth: &LedgerAuth) -> Result<Option<String>, TokenError>Walks the tier chain. Returns
Ok(Some(token))on success,Ok(None)if no token found (cargo may still have its own credentials).require_token(cli_token: Option<&str>, ledger_auth: &LedgerAuth) -> Result<Option<String>, Error>Wraps
resolve_tokenwith actionable error messages — converts eachTokenErrorvariant into a user-friendlyError::TokenResolutionstring explaining what to fix.
Design notes
- Tier 2a and 2b are mutually exclusive within a single resolution — env takes precedence if both are configured
init.rs — init orchestration
Implements the init command: discovers git tags and crates.io state, diffs them, and writes per-crate .crate-seq.toml ledger files.
Workflow
For each workspace member:
- Resolve tag pattern — from existing ledger (if present) or auto-detected
- Discover git tags — using the resolved tag pattern
- Fetch registry state — queries crates.io for existing versions
- Build entries — registry-published versions →
Published/Yanked; git-only tags →Pending - Write ledger — saves
.crate-seq.tomlalongside the crate'sCargo.toml
Error cases
Error::CrateNotFound—--cratefilter names a crate not in the workspaceError::Git— tag discovery failureError::Registry— crates.io HTTP failureError::Ledger— ledger write failure
check.rs — ledger vs registry diff
Implements a read-only operation that diffs the local ledger against live crates.io state and reports the current status of all versions.
Key types
CheckReport— full report:crate_name: Stringregistry_latest: Option<Version>— highest non-yanked version on crates.iopending: Vec<LedgerEntry>— versions awaiting publishskipped: Vec<LedgerEntry>yanked: Vec<LedgerEntry>orphaned: Vec<OrphanedEntry>— pending entries whose backing ref is missing
OrphanedEntry—version+recorded_reffor entries whose git tag or snapshot tarball no longer exists
Public API
check_crate(ledger_path, repo_path, crate_seq_version, snapshot_store) -> Result<CheckReport, Error>Loads the ledger, queries crates.io and git, classifies all entries, detects orphans. Does not modify any state.
Orphan detection
- GitTag entries: checks if the tag name exists in the repo
- Snapshot entries: scans the snapshot store for a
.tar.gzfile whose SHA-256 matches the ledger'sref_field
Error cases
Error::Ledger— ledger load failureError::Registry— crates.io HTTP failureError::Git— git tag discovery failure
validate.rs — pre-publish guards
Two validation checks run before every publish attempt (both dry-run and live).
Public API
validate_no_path_deps(cargo_toml_path: &Path) -> Result<(), Error>Scans for path dependencies. If any are found, returns
Error::PathDependencies { manifest, deps }listing all offenders. Handles bothpath = "..."string values and inline table{ path = "..." }forms.run_cargo_check(cargo_toml_path: &Path) -> Result<(), Error>Spawns
cargo check --manifest-path <path>. ReturnsError::CargoCheck { stderr }on non-zero exit,Error::Subprocessifcargocan't be spawned.
Pipeline integration
In the dry-run pipeline, validation failures are soft outcomes — they produce DryRunOutcome::PathDepsFound or DryRunOutcome::CargoCheckFailed without halting the entire run. In the live pipeline, they propagate as hard errors.
topo.rs — topological sort
Sorts workspace members into dependency tiers for correct publish ordering. Uses Kahn's algorithm (BFS) over the intra-workspace dependency graph.
Public API
topo_sort(all_members, selected_names) -> Result<Vec<Vec<&WorkspaceMember>>, Error>Returns tiered groups: tier 0 has no workspace deps, tier 1 depends only on tier 0, etc. Members not in
selected_namesparticipate in graph construction but don't appear in output.
Dependency parsing
- Reads
[dependencies]and[build-dependencies]tables from each member'sCargo.toml - Skips
[dev-dependencies](they don't affect publish order) - Handles
package = "..."aliased dependencies - Only counts deps that are also workspace members
Cycle detection
If the BFS completes but not all selected members were processed, a cycle exists. Returns Error::DependencyCycle { crate_names } listing the involved crates.
Example
For a workspace where crate-seq-core depends on crate-seq-ledger, crate-seq-git, crate-seq-manifest, crate-seq-registry, and crate-seq-snapshot:
- Tier 0:
crate-seq-ledger,crate-seq-git,crate-seq-manifest,crate-seq-snapshot - Tier 1:
crate-seq-registry - Tier N:
crate-seq-core(depends on all of the above)
pipeline/source.rs — source resolution
Maps a LedgerEntry to a temporary directory ready for packaging.
Public API
resolve_source(entry, repo_path, snapshot_store) -> Result<TempDir, Error>GitTagentries → checks out the tree at the tagged commit into a temp directorySnapshotentries → scans the snapshot store for a.tar.gzmatching the SHA-256 hash inentry.ref_, extracts it into a temp directory
Error cases
Error::Git— checkout failureError::SnapshotNotFound— no tarball matches the hashError::Snapshot— extraction failure
pipeline/dry_run.rs — dry-run pipeline
Validates and packages every pending version without any network writes. Used as the default publish behavior (live publish requires --execute).
Key types
DryRunOutcome— per-version result enum:Pass—cargo package --allow-dirtysucceededPackageFailed(String)— stderr from failed packagePathDepsFound(Vec<String>)— path dep names foundCargoCheckFailed(String)— stderr from failed check
DryRunReport—crate_name+Vec<DryRunVersionResult>(each withversion,tag_ref,outcome)
Public API
publish_dry_run(ledger_path, repo_path, snapshot_store) -> Result<DryRunReport, Error>
Per-version flow
- Source resolution → temp directory
- Path dependency check → soft failure if path deps found
cargo check→ soft failure if check fails- Version rewrite in
Cargo.tomlto set exact version cargo package --allow-dirty→ final validation
Design notes
- Validation failures are soft — recorded as outcomes, not propagated as errors. The report shows which versions would fail without halting the entire run.
- Does not modify the ledger
pipeline/execute.rs — live publish pipeline
The full publish-to-crates.io pipeline with idempotency checking and crash-safe ledger persistence.
Key types
VersionPublishOutcome— enum:Published— successfully published in this runAlreadyPublished— already on crates.io, skippedSkipped— ledger status was alreadySkippedFailed(String)— error message
PublishReport—crate_name+Vec<PublishVersionResult>
Public API
publish_execute(ledger_path, repo_path, token, backoff_config, crate_seq_version, snapshot_store) -> Result<PublishReport, Error>
Per-version flow
- Pre-flight token check — runs once before any version is processed. Fails fast with actionable instructions if a configured token source breaks.
- Idempotency check — queries crates.io. If the version is already published, marks it in the ledger and skips.
- Source resolution — resolves the temp directory from git tag or snapshot
- Validation — path dependency check +
cargo check(hard failures here) - Version rewrite — sets exact version in
Cargo.toml - Package —
cargo package --allow-dirty - Publish — with exponential backoff (base 1s, cap 60s, 5 retries)
- Ledger update —
mark_published()+ save immediately after each version
Crash safety
The ledger is persisted after every individual version — if the process crashes mid-run, the ledger reflects exact progress. On restart, already-published versions are detected via the idempotency check and skipped.
Error handling
- On first failure: saves current ledger state, includes partial results in the report, and stops processing further versions
Error::TokenResolution— pre-flight token failureError::Registry— crates.io HTTP failureError::Subprocess—cargospawn failure
pipeline/workspace_publish.rs — workspace-ordered publish
Orchestrates multi-crate publishing in topological dependency order with index propagation waits between tiers.
Public API
publish_workspace_ordered(ledger_paths, repo_path, token, backoff, crate_seq_version, snapshot_store) -> Result<Vec<PublishReport>, Error>
Flow
- Single-crate fast path — if only one ledger path is given, delegates directly without sorting or index-wait
- Discover members — finds all workspace members
- Topo-sort — produces tiered groups in dependency order
- Publish tier by tier:
- For each tier, publish all member crates
- After each tier (except the last), wait for every newly published version to appear in the crates.io index
- Collect reports — all per-crate
PublishReports aggregated into a singleVec
Why index waits matter
When crate B depends on crate A, cargo publish for B will fail if A's new version hasn't propagated to the crates.io index yet. The tier-based approach with index waits between tiers eliminates this race condition.
Error cases
Error::DependencyCycle— circular workspace dependencies- All errors from the publish pipeline propagated
Error enum
crate-seq-core defines a comprehensive error type covering all failure modes:
| Variant | Source | Description |
|---|---|---|
Git(crate_seq_git::Error) |
git operations | Tag discovery, checkout failure |
Registry(crate_seq_registry::Error) |
crates.io | HTTP failure, rate limiting |
Ledger(crate_seq_ledger::Error) |
ledger I/O | Read/write/parse failure |
Manifest(crate_seq_manifest::Error) |
manifest ops | Version rewrite, path dep detection |
Snapshot(String) |
snapshot ops | Extraction failure |
SnapshotNotFound(String) |
source resolution | No tarball matches SHA-256 |
Io { path, source } |
filesystem | General I/O with path context |
Toml { path, source } |
TOML parsing | Invalid manifest with path context |
Subprocess(String) |
process spawn | cargo cannot be spawned |
CargoCheck { stderr } |
validation | cargo check failed |
PathDependencies { manifest, deps } |
validation | Path deps found in manifest |
TokenResolution(String) |
auth | Actionable token setup message |
DependencyCycle { crate_names } |
topo sort | Circular workspace deps |
CrateNotFound(String) |
init | --crate filter names unknown crate |
MissingPackageName(PathBuf) |
workspace | Member manifest has no [package].name |
Dependency graph
crate-seq-core depends on all five library crates. It is the only crate in the workspace with this property — all other crates are leaf dependencies with no intra-workspace deps (except crate-seq-registry which depends on crate-seq-ledger for BackoffConfig).