Skip to content

decrating: dogfood Shipper for the release workflow#80

Merged
EffortlessSteven merged 1 commit into
mainfrom
feature/decrating-release-workflow
Apr 15, 2026
Merged

decrating: dogfood Shipper for the release workflow#80
EffortlessSteven merged 1 commit into
mainfrom
feature/decrating-release-workflow

Conversation

@EffortlessSteven

Copy link
Copy Markdown
Member

Summary

Rewrites .github/workflows/release.yml to drive the crates.io publish
train with Shipper itself (shipper plan -> shipper preflight ->
shipper publish) instead of raw cargo publish. Closes the credibility
gap between the product's promise and the repo's release path, and is the
Phase 8 follow-up called out in docs/release-v0.3.0-rc.1-manifest.md and
docs/decrating-plan.md.

Workflow shape

Triggered on v*.*.* tag push:

  • msrv-gatecargo check --workspace against Rust 1.92 before anything publishes.
  • build-binaries — cross-platform binary artifacts (Linux/macOS/Windows). Uploaded but NOT attached to a release yet.
  • publish-crates-io — builds and installs shipper, then runs:
    1. shipper plan --format json (upload .shipper/ as shipper-state-plan)
    2. shipper preflight (upload .shipper/ as shipper-state-preflight; fail fast on error)
    3. shipper publish with --policy safe --verify-mode package --readiness-method both --readiness-timeout 15m --verify-timeout 10m --max-attempts 12 --base-delay 10s --max-delay 15m --retry-strategy exponential
    4. cargo search each of the 12 crates to confirm registry visibility
    5. Upload final .shipper/ as shipper-state-final (90-day retention) — always, even on failure.
  • create-release — runs ONLY after publish-crates-io succeeds. Creates the GitHub Release and attaches the platform binaries plus a tarball of the final .shipper/ state as publish evidence.

Triggered via workflow_dispatch:

  • release-rehearse — plan + preflight against a chosen ref, no publishing. Use this before cutting a tag.
  • release-resume — downloads a prior failed run's .shipper/ artifact (artifact_run_id input) and runs shipper resume. The plan-ID check inside shipper is the guardrail against workspace drift between runs.

First-publish rate-limit handling

12 brand-new crates; crates.io allows a 5-crate burst, then 1 new crate per 10 minutes. The workflow does NOT hard-code tier batching or sleeps — shipper publish's retry loop handles HTTP 429 as retryable with exponential backoff (--max-delay 15m is larger than the 10-minute window), and --readiness-method both blocks the next publish on sparse-index AND API visibility of the previous one. Full train ~70-90 minutes; job timeout is 180 minutes; on overrun, release-resume picks up from .shipper/state.json.

Preserved vs rewritten

  • Preserved: build-binaries job and platform matrix, softprops/action-gh-release usage, environment-gated release job.
  • Rewritten: the publish-crates-io job now drives shipper, not cargo publish. The create-release job now depends on successful publish and bundles the .shipper/ state as release evidence.
  • Other workflows (ci.yml, architecture-guard.yml, fuzz.yml, mutation.yml) are untouched.

Docs updates

  • RELEASE_CHECKLIST_v0.3.0.md — replaces the hand-rolled cargo publish --dry-run list with workflow-driven steps (rehearse first, then tag, then resume if needed).
  • docs/release-v0.3.0-rc.1-manifest.md — new section explaining how the workflow executes the train.
  • docs/decrating-plan.md — Phase 8 note marking the release-workflow work complete.

Test plan

  • cargo check --workspace passes locally (verified)
  • cargo build -p shipper-cli --release produces a working binary (verified)
  • YAML is well-formed (verified with python3 -c "import yaml; yaml.safe_load(...)")
  • All uses: action pins match the rest of the repo (verified — same versions as ci.yml)
  • Trigger release-rehearse via workflow_dispatch against this branch (manual step after merge) and confirm plan + preflight complete against crates.io without publishing
  • Dry-run the first real release (e.g. v0.3.0-rc.1) end-to-end only once confidence is high; resume via the release-resume job if interrupted

Rewrite .github/workflows/release.yml to use shipper plan / preflight /
publish / resume instead of raw cargo publish.

- Release runs shipper publish as the train executor
- .shipper/ state persisted as artifact throughout (before plan, before
  preflight, after publish, on any failure)
- GitHub Release only finalized AFTER crates.io publishes succeed
- workflow_dispatch "release-rehearse" job runs plan+preflight dry-run
  for safety verification
- workflow_dispatch "release-resume" job downloads .shipper/ artifact
  from a failed run and continues

Per docs/release-v0.3.0-rc.1-manifest.md and docs/decrating-plan.md §6
Phase 8 follow-up.
@EffortlessSteven EffortlessSteven merged commit 5589418 into main Apr 15, 2026
9 of 16 checks passed

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request transitions the release process to an automated GitHub Actions workflow that dogfoods the shipper tool. The documentation is updated with instructions for release rehearsals, automated publishing, and failure recovery. Review feedback highlights several technical inaccuracies in the documentation regarding the shipper CLI, specifically that the plan command does not persist state or support JSON output, and the binary name is shipper-cli. It also suggests using cargo install --dry-run for more accurate post-publish verification.

Comment on lines +53 to +54
`shipper plan` → upload `.shipper/` → `shipper preflight`
→ upload `.shipper/` → `shipper publish` → `cargo search` verification

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The sequence shipper plan → upload .shipper/ implies that the plan command populates the .shipper/ directory with state artifacts. However, based on the implementation in crates/shipper-cli/src/main.rs, the plan command only prints to stdout and does not write to the state directory. The first command that persists execution state to disk is shipper preflight. The checklist should be updated to reflect that the .shipper/ directory will not contain execution state (like state.json) until after preflight or publish has run.

Comment on lines +189 to +191
2. `cargo build --release -p shipper-cli`, then install `target/release/shipper`
to `/usr/local/bin/shipper` (the binary that will drive the train is the
one we just built from this tag).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The command cargo build --release -p shipper-cli produces a binary named shipper-cli by default. However, the documentation refers to installing target/release/shipper. Unless the binary is explicitly renamed in the shipper-cli crate's Cargo.toml (e.g., via [[bin]] name = "shipper"), the installation step to /usr/local/bin/shipper will fail because the source file will not be found. It is safer to use cargo install --path crates/shipper-cli --bin shipper if the binary name is intended to be shipper.

2. `cargo build --release -p shipper-cli`, then install `target/release/shipper`
to `/usr/local/bin/shipper` (the binary that will drive the train is the
one we just built from this tag).
3. `shipper plan --registry crates-io --state-dir .shipper --format json`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The shipper plan command, as implemented in crates/shipper-cli/src/main.rs, does not currently support the --format json flag. The print_plan function only provides text output. Including this flag in the workflow command will not result in JSON output as the documentation suggests.

Comment on lines +193 to +194
writes the plan summary into `.shipper/plan.txt` (and the engine also
persists its internal plan artefacts under `.shipper/`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The shipper plan command is a diagnostic tool that analyzes the workspace and prints the result to stdout; it does not persist "internal plan artefacts" to the .shipper/ directory. State persistence (writing to state.json) typically begins during the preflight or publish phases which utilize the engine. If the workflow expects the .shipper/ directory to contain state information after only running plan, it will likely find it empty (except for any manually redirected output like plan.txt).

Comment on lines +208 to +209
7. On success, `cargo search <crate>` is run against every published crate
to confirm visibility from a fresh resolver.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While cargo search is a useful sanity check, it queries the registry's web API and search index rather than the resolver logic used during a build or install (which depends on the registry index or sparse index). Consequently, it doesn't strictly confirm visibility "from a fresh resolver". A more robust verification of resolver visibility would be running cargo install --dry-run <crate>.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant