Skip to content

chore(publish-prep): final crates.io publish-readiness gap closure for the 6 publishable crates #241

@githubrobbi

Description

@githubrobbi

Why this issue exists

The UFFS workspace is in excellent shape for crates.io publishing. The infrastructure built up through Phases R3 → R7 of docs/architecture/release-automation-plan.md covers nearly everything a maintainer would otherwise have to assemble by hand. This issue tracks the last small gaps before crates.io publish-day is a one-command operation.

State as of 2026-05-14 (after #236, #237, #238, #239 merged)

✅ Already done

  • Strategic publish scrub (per docs/refactor/crates-io-publishability-deep-dive.md): 6 publishable crates (uffs-time, uffs-text, uffs-mft, uffs-client, uffs-mcp, uffs-cli), 2 deferred-decision crates (uffs-core, uffs-daemon), 6 never-publish.
  • Per-crate READMEs on all 6 publishable crates (docs(publish): add per-crate READMEs for uffs-time and uffs-text #237 + docs(publish): add per-crate READMEs for the 4 post-polars publishable crates #239).
  • manifest-audit allow-list for per-crate readme override (feat(manifest-audit): permit per-crate readme override via allow-list (invariant 3.5) #238).
  • [package.metadata.docs.rs] blocks on all 6 publishable crates (Phase R6).
  • [workspace.lints.rust] missing_docs = "deny" — every pub item compiler-enforced to have a doc comment.
  • crates-io-dry-run.yml scheduled CI workflow — runs cargo publish --dry-run -p <crate> AND cargo semver-checks check-release -p <crate> weekly (Mondays 06:00 UTC) on every publishable crate. Last run: 2026-05-11, ✅ success.
  • release-plz.toml complete: workspace-style tags, workspace CHANGELOG, release_commits filter, git_only = true baseline, release_always = false gate, per-package changelog_path for the 2 ready-today crates, release = false for the polars-blocked + deferred crates.
  • cargo publish --dry-run on uffs-time + uffs-text: ✅ both succeed today (verified locally on 2026-05-14).

������ Remaining gaps (genuine, actionable now, polars-independent)

1. Per-crate keywords + categories override (allow-list mechanism + apply)

Workspace-level defaults in root Cargo.toml:

keywords = ["mft", "ntfs", "file-search", "windows", "polars"]
categories = ["filesystem", "command-line-utilities"]

These are correct for the app (uffs-cli — the search engine binary) but wrong for library crates. Examples of better per-crate metadata:

Crate Suggested keywords Suggested categories
uffs-time ["ntfs", "filetime", "datetime", "windows", "no-std"] ["date-and-time"]
uffs-text ["unicode", "ntfs", "case-folding", "trigram", "i18n"] ["text-processing"]
uffs-mft (current workspace defaults are fine — keep inheriting) (current workspace defaults are fine)
uffs-client ["ipc", "rpc", "client", "uffs", "search"] ["api-bindings", "filesystem"]
uffs-mcp ["mcp", "ai", "model-context-protocol", "rmcp", "uffs"] ["api-bindings", "command-line-utilities"]
uffs-cli (current workspace defaults are correct — keep inheriting) (current workspace defaults are correct)

Requires mirroring the #238 readme-override pattern in scripts/ci/manifest-audit/src/audit.rs:

  • Add KnownExceptions::keywords_override_ok: BTreeSet<&'static str> and categories_override_ok: BTreeSet<&'static str>.
  • Suppress invariant 3.5 firing for listed crates that carry an explicit override.
  • Constrain to reasonable shape (≤ 5 keywords, each ≤ 20 chars; categories must match the crates.io taxonomy — cargo enforces this on publish too).
  • 4-6 new unit tests covering: allow-listed crate suppresses finding, unlisted crate still fires, oversize / invalid keywords still fire even for listed crate, workspace inheritance passes for every member.
  • Apply per-crate keywords + categories on the 4 library crates that benefit (the table above).

Owner: 1 PR, ≈ 1 hour.

2. #![cfg_attr(docsrs, feature(doc_auto_cfg))] in publishable lib.rs files

The [package.metadata.docs.rs] block in each publishable manifest already passes --cfg docsrs to rustdoc. But the per-crate lib.rs files don't opt into the doc_auto_cfg nightly feature, so cfg-gated items (e.g. #[cfg(windows)], #[cfg(feature = "async")]) don't render with their cfg badge on docs.rs.

5 single-line edits (uffs-cli has no lib.rs):

#![cfg_attr(docsrs, feature(doc_auto_cfg))]

Goes at the top of each:

  • crates/uffs-time/src/lib.rs
  • crates/uffs-text/src/lib.rs
  • crates/uffs-mft/src/lib.rs
  • crates/uffs-client/src/lib.rs
  • crates/uffs-mcp/src/lib.rs

The directive is gated behind cfg(docsrs) so local cargo doc (which doesn't pass --cfg docsrs) never exercises the nightly-only feature. Zero risk to local builds.

Owner: 1 PR, ≈10 min.

������ Nice-to-have (low priority, defer or skip)

3. examples/ directories for the library crates

uffs-time, uffs-text, uffs-client, uffs-mcp could each ship a runnable examples/*.rs file demonstrating the canonical use case. Not required by crates.io; high-signal for new visitors browsing the crate detail page. Defer until post-first-publish.

4. Workspace homepage field

Currently no homepage is set in [workspace.package]. Cargo uses repository as the fallback link on crates.io, which is fine. Add homepage = "https://uffs.dev" (or similar) if/when a project site exists.

������ Publish-day actions (gated on first publish or coordination, not actionable today)

These flips are deliberately deferred until the same coordinated PR that performs the first crates.io publish. Doing them earlier would either no-op (semver checks have no baseline) or fire false positives (dry-run gate fails on crate-name lookups that haven't happened yet).

5. Crate name reservation on crates.io (Phase R6 step 6)

Publish stub 0.0.0 versions of all 6 publishable crate names so a squatter can't register them before us. Requires CARGO_REGISTRY_TOKEN and maintainer go-ahead. Reference: docs/architecture/release-automation-plan.md §R6 step 6.

6. Flip FAIL_ON_DRY_RUN_ERROR='true' in crates-io-dry-run.yml

Currently advisory. Flip to hard-gate after R6 step 6 lands so that future regressions surface as a CI failure, not just a workflow-summary annotation.

7. Flip FAIL_ON_SEMVER_BREAK='true' in crates-io-dry-run.yml

Currently no-op (no crates have a baseline on crates.io). Flip to hard-gate after R8 dress rehearsal lands so semver-breaking changes shipped under a non-major bump fail CI.

8. Flip semver_check = true in release-plz.toml

release-plz.toml:302 has it off pending Phase R6/R8. Flip to on at first publish so release-plz’s own release-PR generation enforces semver compliance.

9. Workspace publish = false per-package overrides for uffs-time + uffs-text

The two ready-today crates already carry publish = true (visible in their Cargo.toml). Workspace default stays publish = false until the staggered rollout finishes. No change needed today.

Sequencing

The two actionable PRs are independent and can land in any order:

  1. PR-A: doc_auto_cfg directive on 5 lib.rs files — trivial, mechanical.
  2. PR-B: keywords + categories allow-list + apply — mirror of feat(manifest-audit): permit per-crate readme override via allow-list (invariant 3.5) #238 pattern.

Publish-day actions (5-9) are explicitly out of scope for this issue. When the maintainer is ready for the coordinated publish, those flips happen in one focused PR alongside the actual cargo publish invocation.

Acceptance criteria

  • PR-A merged: doc_auto_cfg directive in uffs-time, uffs-text, uffs-mft, uffs-client, uffs-mcp lib.rs files.
  • PR-B merged: KnownExceptions::keywords_override_ok + categories_override_ok in scripts/ci/manifest-audit/src/audit.rs, per-crate overrides applied where they materially help discoverability (uffs-time, uffs-text, uffs-client, uffs-mcp).
  • After both PRs land: cargo publish --dry-run -p uffs-time -p uffs-text -p uffs-mft -p uffs-client -p uffs-mcp -p uffs-cli succeeds for every crate (today already true for the 2 polars-free ones).
  • After both PRs land: cargo run -p uffs-manifest-audit reports zero findings.
  • Documented in CHANGELOG.md under the next release entry.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions