Phase 2: Reserve crate names on crates.io¶
Prompt¶
Before executing any step of this plan, back brief the operator as to your understanding of the plan and how the work you intend to do aligns with the master plan (PLAN-crate-extraction.md) and the Phase 2 Decision (#7) it records.
This phase has a unique property: it includes a publish to a
public, immutable registry. Once a 0.0.0 crate is uploaded
to crates.io, it cannot be replaced or deleted (only yanked,
and the name is permanently bound to the publishing account).
Treat the publish step with the same care as a force-push: do
the dry-run, eyeball the manifest, and only then publish.
The actual cargo publish command will be run by the operator,
not by Claude. Claude does not have crates.io credentials and
must not attempt to log in or publish. Claude's job in this
phase is to prepare the placeholder crates, run all local
verification including cargo publish --dry-run, and then
hand over to the operator with the exact commands to run.
I prefer one commit per logical change. The "create the three placeholders" change is symmetric across the three crates and the artefacts are inert until published, so it is one commit. The "mark phase complete" status update is a separate commit made after the operator confirms all three publishes succeeded.
Situation¶
After Phase 1 (commits 7191d9e..c3472d6, status 011f78c), the repository is a Cargo workspace:
ryll-repo/
├── Cargo.toml # workspace manifest
├── ryll/
│ ├── Cargo.toml # ryll package, inherits workspace fields
│ └── src/
└── ...
Cargo.toml declares
members = ["ryll"] and shares edition, license,
authors, and repository via [workspace.package].
The master plan (PLAN-crate-extraction.md)
Decision #7 mandates that the three target crate names be
reserved on crates.io with 0.0.0 placeholder publishes
before any extraction work begins:
shakenfist-spice-compressionshakenfist-spice-protocolshakenfist-spice-usbredir
Pre-research checked the registry on the day this plan was
written: all three names returned HTTP 404 from
https://crates.io/api/v1/crates/<name>, meaning they were
unowned and available. (The bare spice-* namespace was also
unowned at the same time, which is a separate consideration —
see Decision #3 in the master plan for why we are not claiming
those.)
This phase has zero impact on ryll's existing build, tests,
release pipeline, or runtime behaviour. The new placeholder
crates are workspace members but contain only a doc-comment in
lib.rs; they are pulled into cargo test --workspace (and
will trivially pass) and cargo clippy --workspace
--all-targets (and will trivially pass), and CI will start
building/checking them automatically once they appear in the
workspace.
Mission and problem statement¶
Reserve the three crate names on crates.io by publishing
minimal, well-documented 0.0.0 placeholder crates from the
Cargo workspace, so that no other party can claim them before
the real 0.1.0 releases land in the upcoming extraction
phases.
After this phase completes:
shakenfist-spice-compression,shakenfist-spice-protocol, andshakenfist-spice-usbredirexist on crates.io as version0.0.0, owned by the operator's crates.io account.- Each placeholder crate's
crates.iopage clearly explains that the crate is a name reservation for the upcoming pure-Rust SPICE extraction work, with a link back to the ryll repository and the master plan. - The three crates exist as workspace members in the local repository, all build cleanly, and CI runs lint/test against them automatically.
- The release of an actual
0.1.0for any of the three crates is a future-phase concern and explicitly out of scope here.
There must be no functional changes to ryll, no dependency changes for ryll, and no build/test regressions.
Approach¶
The phase splits into three discrete activities, each with its own risk profile:
-
Local preparation (Claude, repeatable, reversible). Create the three new workspace member directories with minimal
Cargo.toml,lib.rs, andREADME.mdfiles. Add them to[workspace] members = [...]. Verify thatcargo build --workspace,cargo test --workspace,cargo clippy --workspace --all-targets, andcargo fmt --all --checkall succeed. Runcargo publish --dry-run -p <name>for each crate and inspect the staged tarball to confirm the manifest, license, README, andlib.rslook correct after workspace inheritance is resolved. Commit this. -
Operator publish (operator, irreversible). The operator runs
cargo login(one-time, if not already done) and thencargo publish -p <name>for each of the three crates from the workspace root. Once each upload completes, the operator visits the crates.io page for the crate to confirm it is live and the README rendered correctly. -
Status update (Claude, after operator confirms). Mark Phase 2 as complete in the master plan's execution table, and record any execution discoveries in this phase plan's "Discoveries during execution" section. Commit.
The local preparation commit must leave CI green even before the publishes happen. The publishes are entirely outside the git history — the only artefacts are on crates.io.
Pre-flight verification¶
Before starting Step 1, Claude verifies:
- Working tree is clean on the
display-iterationbranch and Phase 1 is fully committed (git log --oneline -10should show 011f78c at top or close to it). - CI is green on the current commit, via
ci-status shakenfist/ryll runs --branch display-iteration --limit 3. If CI is broken, fix that first. - Names are still available on crates.io. This was true
when the plan was written, but anyone could have published
in the interim. For each of the three names:
All three must return
curl -sI -o /dev/null -w "%{http_code}\n" \ "https://crates.io/api/v1/crates/shakenfist-spice-compression"404. If any returns200, stop and re-plan immediately — someone else has claimed the name. Possible responses: pick a new prefix (e.g.sf-spice-*), or contact the new owner if the existing crate is empty/abandoned. Do not proceed with the rest of Phase 2 until this is resolved. - Operator has a crates.io account and an API token. This does not need to be done by Claude; surface the requirement in the back-brief and let the operator confirm before Step 1 begins. The operator should also confirm whether the account they will publish from is the one that should own these crates long-term (per Decision #7, the names cannot be transferred between accounts without going through the crates.io team or a formal consent message).
If any of the checks above fail, stop and re-plan rather than improvising.
Crate template¶
Each of the three placeholder crates has the same structure:
Cargo.toml¶
Each placeholder crate inherits as much as possible from
[workspace.package] to minimise drift:
[package]
name = "shakenfist-spice-<component>"
version = "0.0.0"
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
description = "Reserved name for an upcoming pure-Rust SPICE <component> implementation extracted from the ryll SPICE client."
readme = "README.md"
Notes:
version = "0.0.0"is the conventional placeholder version. Cargo's resolver treats0.0.0as the lowest version, so any later0.1.xrelease will be picked automatically bycargo addandcargo updateonce it lands.name,version,description, andlicense(orlicense-file) are the four fields crates.io requires for publish to succeed.repositoryis strongly recommended; inheriting it from the workspace ensures consistency.authorsis optional and informational but cheap to inherit.readme = "README.md"is the explicit form. Cargo auto-detectsREADME.mdin the package root if not set, but being explicit makes it obvious what is being uploaded and rendered on the crates.io page.- The description is intentionally long — it appears in
cargo search, the crates.io listing, andcargo metadata, so make every word do work explaining the reservation. - No
[dependencies], no[features], no[dev-dependencies]blocks. Placeholders must be functionally empty. - No
[package.metadata.deb]or[package.metadata.generate-rpm]. These crates are libraries, not binaries, and are not packaged.
src/lib.rs¶
A single line of //! doc-comment is enough to make the crate
build. The doc-comment becomes the rustdoc landing page, so it
should briefly explain what the crate is for and link to the
README:
//! Reserved crate name for an upcoming pure-Rust SPICE
//! <component> implementation extracted from the
//! [ryll](https://github.com/shakenfist/ryll) SPICE client.
//!
//! This crate is currently a name reservation only and
//! contains no functional code. See the README for details
//! and a link to the extraction plan.
No pub items. The crate must compile (and cargo publish
runs a verification build), but it must not export an API
that someone could accidentally start depending on.
README.md¶
The README is the most important artefact in this phase: it is what crates.io renders on the crate's page, and it is the primary signal to anyone who stumbles across the crate that it is a legitimate reservation rather than squatting. The Rust Forge crate ownership policy recommends specific language for reservation crates; the template below incorporates it.
# shakenfist-spice-<component>
**This crate is a functionally empty crate that exists to
reserve the crate name for an upcoming pure-Rust SPICE
<component> implementation extracted from the
[ryll](https://github.com/shakenfist/ryll) SPICE client. It
should not be used.**
## Status
Reserved by the [shakenfist](https://github.com/shakenfist)
project. The real `0.1.0` release will be published when the
extraction work in
[ryll](https://github.com/shakenfist/ryll/blob/develop/docs/plans/PLAN-crate-extraction.md)
lands. Until then, this `0.0.0` release is intentionally
empty.
## What this crate will contain
<one or two sentences specific to each crate — see per-crate
text below>
## Why a placeholder?
crates.io has a flat, immutable, first-come namespace. We are
publishing this empty crate now to prevent the name from being
claimed by an unrelated party (typosquatter, AI-generated junk
crate, well-meaning third party) before the real
implementation is ready. This is consistent with the
[Rust Forge crate ownership policy](https://forge.rust-lang.org/policies/crate-ownership.html)'s
guidance on reservation crates: the README clearly states the
intent, and substantive publishing will follow.
## Project links
- Source repository:
<https://github.com/shakenfist/ryll>
- Extraction plan:
<https://github.com/shakenfist/ryll/blob/develop/docs/plans/PLAN-crate-extraction.md>
- Issues / contact: file via the ryll repository
Per-crate "What this crate will contain" text¶
shakenfist-spice-compression:
When released, this crate will provide pure-Rust implementations of the SPICE image-stream codecs: QUIC (the SPICE wavelet/arithmetic codec, not the QUIC transport protocol), GLZ (dictionary-based cross-frame LZ), LZ (single-frame LZ), and LZ4. Each algorithm will be gated behind a Cargo feature for dependency-minimisation.
The initial
0.1.0release will contain decompression only, matching what the ryll client needs today. The crate name deliberately covers both directions so that compression implementations of the same codecs can be added in future minor releases without a crate rename. SPICE proxies (such as the planned Rust rewrite of the kerbside proxy) and server-side tooling are likely to want compression as well.
shakenfist-spice-protocol:
When released, this crate will provide pure-Rust SPICE protocol primitives: protocol constants and opcodes, wire-format message structs with serialisation, the SPICE link handshake (including TLS and RSA-OAEP authentication), and a
SpiceClientfor connecting to a SPICE server.
shakenfist-spice-usbredir:
When released, this crate will provide a pure-Rust parser and message types for the SPICE USB redirection protocol (the SPICE-side of
usbredir), suitable for clients, proxies, and protocol analysis tools.
Execution¶
Step 1: Create the three placeholder workspace members¶
This is the only Claude-authored commit in Phase 2 (other than the status update at the end). Operator should be ready to do the publish step immediately after this lands, to keep the window between the placeholders existing in git and being claimed on crates.io as short as possible — although in practice the placeholders are only on the local branch until the operator pushes, so the real squatting risk is the time between push and publish.
Steps within the commit:
-
For each of the three crate names, create the directory structure under the workspace root:
...and the same forshakenfist-spice-protocol/andshakenfist-spice-usbredir/. -
Each
Cargo.tomlfollows the template in the "Crate template" section above. Eachlib.rsis the four-line doc-comment. EachREADME.mdfollows the template, with the per-crate "What this crate will contain" paragraph substituted in. -
Update the top-level Cargo.toml
[workspace] memberslist: -
Verify locally inside the devcontainer:
cargo fmt --all --check— passes (no Rust source to format other than the triviallib.rs).cargo clippy --workspace --all-targets -- -D warnings— passes for all four members.cargo build --workspace— builds all four members.cargo build --release -p ryll— ryll builds in release.cargo test --workspace— all 99 ryll tests pass; the three placeholder crates each contribute "0 tests run".cargo deb --no-build -p ryll— produces the sametarget/debian/ryll_0.1.3-1_amd64.debas Phase 1.cargo generate-rpm -p ryll— produces the sametarget/generate-rpm/ryll-0.1.3-1.x86_64.rpmas Phase 1.-
pre-commit run --all-files— passes. -
Critical: dry-run each publish. For each of the three crates, from the workspace root:
Each must succeed. The dry-run will perform the local verification build and packaging steps but will not contact the registry, so it will not catch a name collision or auth failure — those can only happen at real publish time. What it will catch is missing required metadata, an invalid manifest, license file mismatches, and similar local issues. -
Inspect the packaged tarballs.
For each of the threecargo publish --dry-runleaves a.cratefile intarget/package/. Run:.cratefiles, inspect the embeddedCargo.tomlto verify that workspace inheritance has been resolved correctly (the published manifest should contain concreteedition,license,authors,repositoryvalues, not.workspace = truereferences):Confirm the README andtar -xzOf target/package/shakenfist-spice-compression-0.0.0.crate \ shakenfist-spice-compression-0.0.0/Cargo.tomllib.rsare also present in the tarball listing: Expected files:Cargo.toml,Cargo.toml.orig,README.md,src/lib.rs,.cargo_vcs_info.json, and possiblyCargo.lock. Notarget/, no.git/, no leftover artefacts. -
Commit. The commit should NOT include the
target/package/directory; it is in.gitignorevia the existingtarget/rule.
Commit message (subject ≤ 50 chars):
Add placeholder crates for crates.io reservation.
Step 2: Operator publishes to crates.io¶
This step is performed by the operator, not Claude. Claude should hand off with the exact command sequence and a reminder of the irreversibility.
Operator pre-flight:
- Confirm
cargo loginhas been run on the publishing machine, with the correct API token from https://crates.io/me (or whichever account is intended to own these crates long-term). - Re-verify the names are still available immediately before
publishing (the same
curlcheck as the Phase 2 pre-flight). Belt-and-braces against a same-day claim. - Decide whether to push the branch to GitHub before or after publishing. Pushing first means the README's repository link will work the moment the crate goes live; publishing first means the crate exists before the placeholder commit is visible to anyone. Recommendation: push first, so the crates.io page links to a real branch.
Publish sequence (run from the workspace root):
cargo publish -p shakenfist-spice-compression
cargo publish -p shakenfist-spice-protocol
cargo publish -p shakenfist-spice-usbredir
The order does not matter; the crates are independent. After each one completes, visit:
- https://crates.io/crates/shakenfist-spice-compression
- https://crates.io/crates/shakenfist-spice-protocol
- https://crates.io/crates/shakenfist-spice-usbredir
...and confirm:
- The version is
0.0.0. - The README is rendered (markdown headings, links).
- The "Repository" link points to
github.com/shakenfist/ryll. - The "License" is
Apache-2.0. - The owner is the expected crates.io account.
If any of those look wrong, do not publish a 0.0.1 to
fix it — fix the source in a follow-up commit and decide
whether the cosmetic issue is worth burning a version. The
README content is the only thing that genuinely matters; a
typo there is fine to live with on 0.0.0 since the real
release will be 0.1.0 with a fresh README.
Step 3: Mark Phase 2 complete¶
After the operator confirms all three publishes succeeded:
- Update the master plan's execution table:
- Add a "Discoveries during execution" section to this phase plan capturing anything surprising (e.g. dry-run caught something unexpected, the operator's account had a 2FA wrinkle, the crates.io UI rendered the README oddly).
- If any of the open questions below were answered during execution, fold the answers back into the relevant Decision in the master plan, or into a new note in this phase plan.
- Commit.
Commit message (subject ≤ 50 chars):
Mark Phase 2 (crate name reservation) complete.
Open questions¶
-
Which crates.io account should own the placeholders? The operator (mikalstill) is the expected owner. Whether to immediately add a second co-owner (e.g. a shakenfist organisation account or another shakenfist team member's personal account) for bus-factor purposes is a policy question for the operator. If yes, the
cargo owner --add <user> <crate>invocation can be done right after publish. Defer the decision; do not block this phase on it. -
Should we also publish to a private registry? No. Decision #1 in the master plan establishes that internal consumers (kerbside) will use git dependencies, not the registry. The crates.io publishes are purely about name reservation. Re-confirming here so it does not get re-litigated mid-phase.
-
Yank
0.0.0after0.1.0publishes? Decision #7 notes that yanking is optional after the real release lands. The Rust Forge ownership policy actually recommends not yanking placeholder crates — the README is what explains the reservation to a confused user, and yanking hides that explanation fromcargo search. Defer the yank decision to the future-work phase that publishes0.1.0; do not commit to yanking here. -
Do we need to push the branch before publishing? Strongly recommended (so the README's GitHub links work immediately when the crate goes live), but not strictly required by cargo-publish. The publishing operator can decide based on their workflow. Note that pushing the branch is a separate operator action that the user has asked Claude not to do automatically.
Administration and logistics¶
Success criteria¶
We will know Phase 2 has been successfully implemented when:
- The repository contains three new workspace member
directories:
shakenfist-spice-compression/,shakenfist-spice-protocol/, andshakenfist-spice-usbredir/, each withCargo.toml,README.md, andsrc/lib.rs. - The top-level Cargo.toml
memberslist contains all four crates (ryll plus the three placeholders). cargo build --workspace,cargo test --workspace,cargo clippy --workspace --all-targets -- -D warnings,cargo fmt --all --check, andpre-commit run --all-filesall succeed locally.- CI is green on the branch after the placeholder commit.
cargo publish --dry-run -p <name>succeeded for all three placeholders, and the embeddedCargo.tomlin eachtarget/package/<name>-0.0.0.cratecontains concrete workspace-inherited values (not.workspace = true).- All three crates exist on crates.io as version
0.0.0, owned by the operator's account. - Each crates.io page renders the README correctly, with working links back to the ryll repository and the master plan.
- The master plan's execution table shows Phase 2 as Complete.
- No functional changes to ryll:
ryll/Cargo.tomlis unmodified,ryll/src/is unmodified, all 99 existing tests still pass, and the.deband.rpmpackaging still works with-p ryll.
Future work¶
0.1.0publishes happen at the end of Phases 3, 4, and 5 (compression, protocol, usbredir respectively), once each crate's API stabilises. That work is tracked under the master plan's "Future work" section, not here.- Adding co-owners on crates.io for bus-factor — see open question #1. If/when this happens, document the decision in the master plan's Decision #7 as an addendum.
- Crates.io trusted-publishing via GitHub Actions is a
newer feature that lets a repo's CI publish to crates.io
using OIDC tokens instead of long-lived API tokens. Worth
evaluating for the eventual
0.1.0release flow, but out of scope for the initial reservation since the operator is publishing manually from a trusted local machine. - Reserving
shakenfist-spice-clientand other future names. If Phase 6 (theSpiceClientextraction) decides the eventual home ofSpiceClientis a standaloneshakenfist-spice-clientcrate rather than a member ofshakenfist-spice-protocol, that name should also be reserved at the time the decision is made. Out of scope here.
Bugs fixed during this work¶
(None expected. Phase 2 is purely additive — three new workspace members. If a clippy or test issue surfaces in the new placeholder crates, fix it inline; if a pre-existing ryll issue is uncovered, fix it in a separate commit before the placeholder commit lands.)
Discoveries during execution¶
- Mid-phase rename from
decompressiontocompression. After Step 1 was already partially in progress (the decompression placeholder directory had been created and the protocol placeholder was being written), the operator asked to rename the first crate toshakenfist-spice- compressionso the eventual crate could cover both directions of the SPICE codecs in a single namespace. This was handled by: - Renaming the in-flight (untracked) directory.
- Re-verifying
shakenfist-spice-compressionwas also available on crates.io (it was, 404). - Updating Decision #6 in the master plan to capture the naming rationale, the master plan's other references to the old name, and both phase plan files.
-
Committing the rename as a standalone documentation-only commit (26c38c0) before the placeholder commit (42c8e8a), so the two changes stayed logically separate. The rename did not require any extra crates.io action — the old name was never published, only mentioned in yet-to-be-committed plan text.
-
Operator publishes via docker, not native cargo. The operator's machine has docker but no native cargo installation. Publishes were performed by passing the short-lived crates.io API token into the existing
ryll-devdevcontainer image via-e CARGO_REGISTRY_TOKEN=..., with aforloop wrappingcargo publish -p <name>. This worked cleanly: docker has network access by default,CARGO_REGISTRY_TOKENis read by cargo without needing a priorcargo login, and the token never touched disk inside the container (no~/.cargo/credentials.tomlwrite). Worth remembering for future publish operations. -
Token scoping recommendation. The publish was performed with a
publish-new-only token scoped to the three crate-name patterns and an expiration of 1 day. This is the correct security posture for a one-shot reservation publish: even if the token had leaked, an attacker could not have used it to publish updates to existing crates, change ownership, yank, or publish new crates outside the reservation namespace. -
All three publishes succeeded within ~7 seconds. The three uploads landed at 2026-04-08T11:31:06, 11:31:09, and 11:31:13 UTC respectively. crate sizes 1.6-2 KB each. No retries needed, no metadata rejections, no rate limiting. Cargo's
--dry-runcorrectly predicted the package contents and the verification build succeeded for all three before the real publish. -
.cargo_vcs_info.jsonwas added to the published tarballs. During local dry-run inspection (Step 1), the packaged tarballs contained 5 files (Cargo.toml, Cargo.toml.orig, Cargo.lock, README.md, src/lib.rs) but not.cargo_vcs_info.json, because the working tree had uncommitted changes when the dry-run ran. Once the placeholders were committed (42c8e8a) and the operator published from the clean tree, the VCS info file was added automatically by cargo. This is expected cargo behaviour and was correctly anticipated in Step 1's tarball inspection notes. -
No need to push the branch first. The Phase 2 plan flagged this as a recommendation (open question #4), but in practice the README links pointing at
develop/docs/plans/PLAN-crate-extraction.mdwill 404 only untildisplay-iterationis merged todevelop, which is expected to happen as part of normal merge cadence. Since the crates.io pages will be visited primarily by us in the short term (and by anyone runningcargo search shakenfistwho is curious), the broken link for the first day or two is not a meaningful problem. -
Verification via the crates.io HTTP API. Rather than manually clicking through three crates.io pages in a browser, all the success-criteria checks (version, license, repository, owner, yanked status) were verified by hitting
https://crates.io/api/v1/crates/<name>and parsing the JSON. This is more thorough than visual inspection and is recorded here in case future phases want to do the same for0.1.0publish verification.
Back brief¶
Before executing any step of this plan, back brief the operator as to your understanding of the plan and how the work you intend to do aligns with that plan. Specifically confirm:
- That Claude will prepare the placeholder crates and run
all local verification including
cargo publish --dry-run, but will not run the realcargo publish— that is the operator's job. - That the three crate names (
shakenfist-spice-*) are verified available on crates.io as part of pre-flight. - That the operator has confirmed they have a crates.io
account with
cargo loginconfigured, and that the account is the one intended to own these crates long-term. - That the placeholder commit will leave CI green even before the publishes happen, and that the publishes are entirely outside the git history.
- The four-step sequence: (pre-flight) → (Step 1: prepare and commit) → (Step 2: operator publishes) → (Step 3: mark complete).