crate-seq-ledger
Overview
crate-seq-ledger is the type foundation of the workspace. It defines the core domain types, the state machine for version lifecycle transitions, query helpers, file I/O, and tag-pattern utilities. Every other crate in the workspace imports from it.
Root config — CrateSeqLedger
The root struct mirrors the .crate-seq.toml file structure:
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct CrateSeqLedger {
#[serde(rename = "crate")]
pub crate_config: CrateConfig,
pub settings: LedgerSettings,
pub auth: LedgerAuth,
#[serde(rename = "versions", default)]
pub entries: Vec<LedgerEntry>,
}CrateConfig
pub struct CrateConfig {
pub name: String, // crate name from Cargo.toml
pub registry: Option<String>, // registry alias, defaults to crates.io
}LedgerSettings
pub struct LedgerSettings {
pub mode: Option<String>, // "git-tag" or "snapshot"
pub tag_pattern: Option<String>, // glob for tag matching, e.g. "v*"
pub dry_run_default: Option<bool>, // if true, dry-run by default
pub backoff_base_ms: Option<u64>, // base delay for exponential backoff
}LedgerAuth
pub struct LedgerAuth {
pub token_env: Option<String>, // env var holding registry token
pub token_cmd: Option<String>, // shell command whose stdout is the token
}All config structs derive Default and use #[serde(default)] on optional fields, so partial .crate-seq.toml files deserialize without error.
Entry types — types.rs
LedgerEntry
pub struct LedgerEntry {
pub version: semver::Version,
pub source: VersionSource,
#[serde(rename = "ref")]
pub ref_: String, // tag name or snapshot SHA-256
pub status: LedgerStatus,
}VersionSource
#[serde(rename_all = "kebab-case")]
pub enum VersionSource {
GitTag,
Snapshot,
}LedgerStatus
#[serde(rename_all = "kebab-case")]
pub enum LedgerStatus {
Pending,
Published,
Skipped,
Yanked,
}All types derive Serialize and Deserialize with kebab-case renaming, ensuring TOML round-trip fidelity.
State machine — state.rs
Each method locates the entry by version, validates the current status, and transitions:
| Method | Valid from | Target | Error on invalid |
|---|---|---|---|
mark_published(&mut self, version) |
Pending, Skipped |
Published |
InvalidTransition |
mark_skipped(&mut self, version) |
Pending, Skipped, Yanked |
Skipped |
InvalidTransition |
mark_yanked(&mut self, version) |
Published |
Yanked |
InvalidTransition |
All methods return Result<(), Error>. If the version is not found in entries, they return Error::VersionNotFound. If the current status is not in the "Valid from" set, they return Error::InvalidTransition(version, current_state, target_state).
Transition rules
- Published → Published is rejected (idempotent publishes go through the pipeline's
AlreadyPublishedpath instead) - Yanked → Skipped is allowed — enables re-skipping a yanked version
- Yanked → Yanked is rejected — no double-yank
Query methods — query.rs
/// entries with status Pending, sorted ascending by version.
#[must_use]
pub fn pending_versions(&self) -> Vec<&LedgerEntry>;
/// entries with status Published, sorted ascending by version.
#[must_use]
pub fn published_versions(&self) -> Vec<&LedgerEntry>;
/// first entry whose version equals `v`.
#[must_use]
pub fn find_version(&self, v: &semver::Version) -> Option<&LedgerEntry>;Both pending_versions and published_versions return entries sorted ascending by SemVer.
I/O functions — io.rs
load
pub fn load(path: &Path) -> Result<CrateSeqLedger, Error>Reads the file at path as a string, then deserializes via toml_edit::de::from_str. Returns Error::Io on read failure or Error::Deserialize on parse failure.
save
pub fn save(path: &Path, ledger: &CrateSeqLedger) -> Result<(), Error>Serializes with toml_edit::ser::to_string_pretty, writes to a .tmp sidecar, then renames atomically. If rename fails, the .tmp file is cleaned up before returning the error.
Atomicity guarantee
The write-then-rename pattern ensures that a crash mid-write never leaves a corrupted .crate-seq.toml. The .tmp file is always cleaned up on rename failure.
Tag pattern utilities — tag.rs
detect_tag_pattern
pub fn detect_tag_pattern(crate_name: &str, is_workspace: bool) -> StringReturns "v*" for single-crate repos, "{crate_name}-v*" for workspaces.
extract_semver_from_tag
pub fn extract_semver_from_tag(tag: &str, pattern: &str) -> Option<semver::Version>Strips the literal prefix (pattern minus trailing *) from tag, then attempts semver::Version::parse on the remainder. Returns None if the prefix doesn't match or the remainder isn't valid SemVer.
Error type — error.rs
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("TOML deserialize: {0}")]
Deserialize(#[from] toml_edit::de::Error),
#[error("TOML serialize: {0}")]
Serialize(#[from] toml_edit::ser::Error),
#[error("version {0} not found in ledger")]
VersionNotFound(semver::Version),
#[error("invalid state transition for {0}: {1} → {2}")]
InvalidTransition(semver::Version, &'static str, &'static str),
}#[from] attributes enable automatic conversion via ? from std::io::Error, toml_edit::de::Error, and toml_edit::ser::Error.
Source files
| File | Responsibility |
|---|---|
config.rs |
Root config structs (CrateSeqLedger, CrateConfig, LedgerSettings, LedgerAuth) |
types.rs |
Entry types (LedgerEntry, VersionSource, LedgerStatus) |
state.rs |
State machine transitions (mark_published, mark_skipped, mark_yanked) |
query.rs |
Query helpers (pending_versions, published_versions, find_version) |
io.rs |
Atomic TOML load/save |
tag.rs |
Tag pattern detection and SemVer extraction |
error.rs |
Crate error enum |
lib.rs |
Module declarations and re-exports |