Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/dwarves

.nornir/release-doctor-rejection-coverage.md

release doctor — cargo/crates.io rejection-coverage matrix

Durable loop state for the nornir release doctor preflight-hardening effort. Every known way a cargo publish / crates.io upload can be REJECTED or go wrong, mapped to COVERED (which check + file:line) or OPEN / RUNTIME-ONLY.

Preflight framework: src/release/preflight.rs (IssueKind + detector fn + run() wiring + unit test each); src/release/registry.rs (crates.io probes: version-live, ownership, published-max). Doctrine: best-effort — a check whose tooling is unavailable degrades to a NOTE, never a false block.

Authoritative sources consulted: Cargo Book (manifest/publishing/registry-web-api), crates.io src/controllers/krate/publish.rs, cargo cargo-util-schemas restricted_names.rs. Key exact rules captured inline below.

Legend: ✅ COVERED · 🟡 PARTIAL (needs refinement) · ⬜ OPEN · 🔵 RUNTIME-ONLY (backend/network-only; can't be preflighted deterministically).

Matrix

# Rejection class crates.io / cargo rule Status Check (IssueKind / fn @ file:line)
1 Non-member -p target (detached [workspace]) cargo 101 "did not match any packages" ✅ NonMember / run @ preflight.rs:270
2 Missing required metadata (description/license/repository) crates.io: description+license required ✅ MissingMetadata / missing_metadata_fields @ preflight.rs:701
3 Dep-API skew (edited-without-bump upstream) verify build uses unpublished API ✅ VerifyFailed / verify_one @ preflight.rs:869
4 Version already live (immutable clash) "crate version X is already uploaded" ✅ RegistrySkew / registry_skew_issue @ preflight.rs:745
5 Local version BEHIND registry (desync) dependent verifies newer published API ✅ RegistryDesync / registry_desync_issue @ preflight.rs:778
6 Versioned path dev-dep on sibling resolved on publish though dev-deps stripped ✅ VersionedPathDevDep @ preflight.rs:567
7 Path dep without version stripped path → version-less dep rejected ✅ UnpublishableDep / gate::path_dep_audit @ preflight.rs:367
8 Versioned dep on publish=false sibling sibling never on crates.io ✅ UnpublishableDep / extra_manifest_gaps @ preflight.rs:666
9 readme path missing cargo can't read declared readme ✅ BadManifest / extra_manifest_gaps @ preflight.rs:607
10 Keyword count >5 / per-keyword >20 / bad chars / bad start max 5, ≤20, start alnum, [A-Za-z0-9_-+] ✅ BadManifest / extra_manifest_gaps @ preflight.rs:618 (refined: start-alnum + +)
11 Feature refs dep:foo not in [dependencies] cargo "dep:foo not a dependency" ✅ BadManifest / extra_manifest_gaps @ preflight.rs:640
12 Same-version source drift (edited, not bumped) dependent resolves stale registry copy ✅ StaleSourceDrift / source_drift_issues @ preflight.rs:439
13 Crate name owned by someone else (squat) 403 "crate exists but you don't own it" ✅ ownership_blocks @ registry.rs:340 (wired nornir.rs:11408)
14 Wildcard * dependency constraint "wildcard (*) dependency constraints are not allowed on crates.io" ✅ BadManifest / extra_manifest_gaps (for_each_published_dep) @ preflight.rs:687
15 Git dependency without version publish requires version on git/path deps ✅ UnpublishableDep / extra_manifest_gaps @ preflight.rs:707
16 Invalid / non-SPDX license expression "unknown or invalid license expression" (spdx crate); / separator invalid ✅ BadManifest / spdx_license_problem + license_spdx_issue @ preflight.rs:596 (wired run @ ~305)
17 Crate name invalid (len>64, charset, first-char, reserved/Windows) ASCII [A-Za-z0-9_-], start alpha, ≤64, no reserved/Windows-device names ✅ InvalidCrateName / crate_name_problem @ preflight.rs:596 (wired run @ ~272)
18 Category count >5 (hard error) "expected at most 5 categories per crate" ✅ BadManifest / extra_manifest_gaps @ preflight.rs:~645
19 license-file path missing (or non-UTF-8) cargo can't read declared license-file ✅ BadManifest / declared_file_gap @ preflight.rs:~615
20 README not valid UTF-8 cargo reads readme as UTF-8 string ✅ BadManifest / declared_file_gap @ preflight.rs:~615
21 URL field not http(s):// (repository/homepage/documentation) "URL for field must begin with http:// or https://" ✅ BadManifest / extra_manifest_gaps @ preflight.rs (url block)
22 Version string not valid semver "X is an invalid semver version" ✅ BadManifest / run() version-parse guard @ preflight.rs
23 edition not a known value (2015/2018/2021/2024) cargo manifest parse error ✅ BadManifest / extra_manifest_gaps (edition block)
24 rust-version malformed must be digits/periods only, no ^/pre-release ✅ BadManifest / extra_manifest_gaps (rust-version block)
25 description >1000 chars "The description is too long" ✅ BadManifest / extra_manifest_gaps (description block)
26 links value collision (two crates same links) links must be globally unique ✅ (intra-release) BadManifest / duplicate_links_issues @ preflight.rs; cross-registry claim 🔵
27 Tarball >10 MiB (upload size) "max upload size is: N" (default 10 MiB) ✅ (verify-On) OversizedTarball / oversized_tarball_issue @ preflight.rs (inspects the dry-run .crate)
28 Feature refs a missing feature/dep name cargo "feature a includes b which is neither" ✅ BadManifest / extra_manifest_gaps (f) + manifest_all_dep_names
29 Cross-registry dependency (registry=) "Dependency hosted on another registry ... not permitted" ✅ BadManifest / extra_manifest_gaps (cross-registry block)
30 Invalid category SLUG (not in category_slugs) WARNING only (crate still publishes) 🔵 advisory-only; not a hard reject
31 Too-similar name (differs only by -/_/case) crates.io 400 🔵 backend-only; needs live index probe
32 Reserved-name table (dynamic) "reserved name" 🔵 backend table; Windows-device subset preflightable in #17
33 Rate limit (versions/24h, new crates/week) 429 / "too many versions" 🔵 already handled at publish w/ retry (publish.rs)
34 Unverified email / no token "verified email required" / 403 🔵 token/login checked in ownership preflight
35 Max features / max deps per crate configurable caps (large) 🔵 limits are high + server-configurable
36 Versioned CROSS-REPO path= masks the published crate local build resolves the path, never the registry version — a fresh consumer / --verify uses crates.io ✅ (advisory) path_masks_published_notes / cross_repo_versioned_path_deps @ preflight.rs (NOTE; drift-in-release-set already blocks via #12)
37 PROACTIVE dep-API skew (member calls dep::sym absent from the published dep) the E0425 cannot find … in crate <dep> a cargo publish --verify finds after a multi-minute build — the release-night skade::ingest_multi (present in local skade 0.5.1, not published 0.5.0) ✅ (verify-On) ApiSkew (CORRECTNESS blocker) / api_skew_issues + api_skew::{referenced_symbols,public_api_names,missing_symbols} @ preflight.rs — STATIC form of #3: for each sibling path-dep that resolves to an already-published version (not being republished this run), download the published .crate, extract its public API, diff the member's referenced symbols. Caught in ms, before any verify build.
38 FORWARD semver hazard (OUR bump under-states a break WE ship) a removed/renamed/retyped pub item under a patch/minor bump → every downstream compile-breaks on their next resolve; crates.io is immutable so the old version breaks them forever (the cargo-semver-checks/release-plz class) ✅ (P1) SemverHazard / gate_a_wiring::merge_semver + semver_checks::{assess_targets,bump_between,understates,parse_required_bump} — SHELLS the cargo-semver-checks dev-tool binary via the SemverProber seam (pure verdict core; lib needs nightly rustdoc + a heavy stack, impractical in a lean forge). Reverse of #37. Tool absent/unparseable ⇒ note, never a false block. GateALegs.semver DEFAULT-ON; --gate-a semver.
39 Live-but-vulnerable / advisory-yanked TRANSITIVE dep (RUSTSEC/OSV) a Cargo.lock-pinned dep with a known CVE / unsound / advisory-yank still builds — our resolver-level yank check reads the INDEX and misses it (the "we shipped a CVE" class) ✅ (P2) VulnerableDep / gate_a_wiring::merge_advisory + advisory::{classify,assess_lockfile} — REUSES the rustsec library (the engine cargo-audit drives), default-features = false (no reqwest) over a VENDORED advisory-db (Database::open, hermetic). Vulnerable/unsound/yanked = HARD; unmaintained/notice = note. Missing DB/lockfile ⇒ note. GateALegs.advisory DEFAULT-ON; --gate-a advisory|rustsec|audit.
40 Disallowed license (SPDX allowlist) a copyleft/unapproved license SPDX expression sneaking in — presence-only metadata (#2/#16) checks it EXISTS, not that it is PERMITTED (the cargo-deny licenses class) ✅ (P3, opt-in policy) PolicyViolation / packaging_lint::classify_license — REUSES the spdx crate's id list + boolean evaluator, LAX-parsed (accepts /-as-OR + deprecated ids real manifests carry). Empty allowlist ⇒ OFF (no policy ⇒ no opinion). Rides the DEFAULT-ON packaging leg.
41 Banned crate in the resolved graph a deny-listed crate anywhere in Cargo.lock (the cargo-deny bans class) ✅ (P3, opt-in policy) PolicyViolation / packaging_lint::classify_lockfile_policy — REUSES rustsec::Lockfile to read the lock; empty banlist ⇒ OFF. Rides the packaging leg.
42 Duplicate crate versions one crate at ≥2 versions → tree bloat / wider audit+MSRV surface (cargo-deny bans multiple-versions) ✅ (P3, advisory) packaging_lint::classify_lockfile_policy → ADVISORY DuplicateVersion (note, never blocks); DEFAULT-ON.
43 Dep silently raises MSRV / docs.rs build break a dependency whose own rust-version exceeds our declared floor lifts our effective MSRV above what we promise (row was 🟡); and an --all-features docs.rs build that fails AFTER the irreversible publish (immutable version, broken docs forever) ✅ (P4) edition.rs: dep_msrv_advisories (advisory dep-MSRV-raise, DEFAULT-ON) + verify_msrv_build (cargo +<floor> check, heavy, default-OFF) + docs_rehearsal (cargo doc --all-features, heavy, default-OFF, docs_errors HARD when run) + parse_doc_errors; all behind EditionGateConfig, default keeps is_clean semantics. Toolchain/tool absent ⇒ note.

Progress log