Skip to content

Release v0.3.0-rc.1#23

Merged
EffortlessSteven merged 56 commits into
mainfrom
release/v0.3.0-rc.1
Mar 8, 2026
Merged

Release v0.3.0-rc.1#23
EffortlessSteven merged 56 commits into
mainfrom
release/v0.3.0-rc.1

Conversation

@EffortlessSteven

Copy link
Copy Markdown
Member

Summary

This PR prepares Shipper for its first major Release Candidate (v0.3.0-rc.1). It consolidates several months of architectural improvements, performance optimizations, and enterprise-grade feature build-outs.

🚀 Key Features

  • Multi-Registry Publishing: Orchestrate publishes across multiple registries in one command.
  • Sparse Index Caching: ETag-based disk caching for significantly faster readiness verification.
  • Selective Resumability: New --resume-from\ flag for fine-grained recovery control.
  • Deep Diagnostics: Enhanced \doctor\ command with real-world connectivity and permission checks.
  • CI/CD Native: Global --quiet\ mode and optimized progress reporting for non-interactive logs.

🛠️ Maintenance

  • Modularization: Fully enabled \micro-all\ architecture by default in the CLI.
  • Locking: Implemented workspace-aware locking using path hashing to prevent global collisions.
  • Dependency Bumps: Migrated to latest major versions of core dependencies (
    and\ 0.10, \ oml\ 1.0, \dirs\ 6.0, \indicatif\ 0.18).
  • GitHub Actions: Updated all workflow actions to latest stable versions.

✅ Quality Assurance

  • Tests: All 317+ tests passing across all platforms.
  • Lints: Zero clippy warnings under strict -D warnings.
  • Docs: Updated README and technical guides to reflect 0.3.0 capabilities.

- Introduced `shipper-output-sanitizer` crate for sanitizing cargo command output.
- Implemented sensitive data redaction in logs, including authorization tokens and cargo registry tokens.
- Added tests for redaction functionality and integration tests for various credential shapes.
- Created `shipper-progress` crate for CLI progress reporting utilities.
- Implemented a `ProgressReporter` for tracking package publishing progress with TTY support.
- Added integration tests for retry strategies in the `shipper-retry` crate.
- Enhanced policy application logic in the `shipper` crate to respect runtime flags.
- Added fuzzing targets for configuration parsing, runtime options, and output sanitization.
- Created integration tests for chunking functionality in the `shipper-engine-parallel` crate.
- Deleted `types_micro.rs` and `webhook_micro.rs` files as they are no longer needed.
- Updated `webhook.rs` to streamline webhook configuration and event handling.
- Replaced custom webhook configuration with `shipper-webhook` microcrate's configuration.
- Simplified event handling and payload structure for webhook notifications.
- Improved error handling and logging for webhook delivery failures.
- Added `shipper-retry` dependency to `fuzz/Cargo.toml` for enhanced retry logic.
- Cleaned up tests related to webhook functionality and ensured they align with new structure.
…atures

- Fix flaky \
eadiness_modes\ test by extending mock server timeout to prevent premature socket closure before \shipper\ initialization.

- Implement granular workspace locking using path hashing in \shipper-lock\ and \shipper/lock.rs\ to fix the global \~/.shipper/lock\ collision bug, enabling parallel distinct workspace publishing.

- Introduce robust atomic file operations for lock acquisition to close race conditions between concurrent \shipper\ instances.

- Optimize \
eadiness.md\ checks by adding ETag-based disk caching for Cargo sparse index fragments, reducing bandwidth and registry throttling.

- Expand \shipper-cargo-failure\ classification to reliably detect edge-case network errors (500s, DNS, timeouts) as retryable.

- Support deep visibility into dry-run failures in \PreflightReport\ models to improve user debugging.

- Add \--quiet\ flag support across \shipper-progress\ and \shipper-cli\ for CI/CD integration.

- Ensure \micro-all\ features are default-enabled for the CLI to use modular components.

- Modernize \shipper-config\ by adding and validating \schema_version\ in \.shipper.toml\.
…i-registry publishing

- Implement ETag-based disk caching for Cargo sparse index to accelerate readiness polling.

- Add \--resume-from <pkg>\ support to sequential and parallel engines for flexible recovery.

- Enhance \shipper doctor\ with health checks for registry connectivity, git state, and permissions.

- Support multi-registry publishing in CLI with state directory segregation by registry name.

- Update CI templates and snippets to include \--quiet\ flag and better state caching patterns.

- Fix clippy warnings and ensure workspace compilation with all features enabled.

- Synchronize monolithic and modularized implementations for all new features.
- Document \--resume-from\ and multi-registry support in README.

- Expand CI commands section with CircleCI and Azure DevOps.

- Include \completion\ command in README.

- Stabilize BDD tests by increasing mock server timeouts to 60s.
- Bump workspace version to 0.3.0.

- Update CHANGELOG.md with 0.3.0 entry.

- Update ROADMAP.md current release to v0.3.0.

- Add RELEASE_NOTES_v0.3.0.md and RELEASE_CHECKLIST_v0.3.0.md.

- Synchronize dependency versions in Cargo.toml files.

- Final pass of clippy and test fixes for release readiness.
- Handle Dependabot dependency updates (major bumps for dirs, rand, indicatif, toml).

- Update GitHub Actions versions in workflows and templates.

- Bump workspace version to 0.3.0-rc.1.

- Migrate shipper-retry to rand 0.10.0 API.

- Final pass of release assets and checklist.
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai

coderabbitai Bot commented Feb 28, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@EffortlessSteven has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 20 minutes and 29 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 119bd442-41b7-4bc0-adca-e16b281a3f59

📥 Commits

Reviewing files that changed from the base of the PR and between e8ac2a9 and 4cedaa8.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • .gitignore
  • RELEASE_CHECKLIST_v0.3.0.md
  • crates/shipper-auth/CLAUDE.md
  • crates/shipper-auth/README.md
  • crates/shipper-cargo-failure/CLAUDE.md
  • crates/shipper-cargo-failure/README.md
  • crates/shipper-cargo/CLAUDE.md
  • crates/shipper-cargo/Cargo.toml
  • crates/shipper-cargo/README.md
  • crates/shipper-cargo/tests/cargo_commands.rs
  • crates/shipper-chunking/CLAUDE.md
  • crates/shipper-chunking/README.md
  • crates/shipper-cli/CLAUDE.md
  • crates/shipper-cli/tests/bdd_doctor.rs
  • crates/shipper-cli/tests/bdd_micro_backends.rs
  • crates/shipper-cli/tests/bdd_preflight.rs
  • crates/shipper-cli/tests/bdd_publish.rs
  • crates/shipper-cli/tests/bdd_resume.rs
  • crates/shipper-cli/tests/bdd_status.rs
  • crates/shipper-cli/tests/bdd_workflow.rs
  • crates/shipper-cli/tests/cli_e2e.rs
  • crates/shipper-cli/tests/e2e_doctor.rs
  • crates/shipper-cli/tests/e2e_expanded.rs
  • crates/shipper-cli/tests/e2e_preflight.rs
  • crates/shipper-cli/tests/e2e_resume.rs
  • crates/shipper-cli/tests/e2e_status.rs
  • crates/shipper-config-runtime/CLAUDE.md
  • crates/shipper-config-runtime/README.md

Walkthrough

This PR prepares the Shipper 0.3.0-rc.1 release by extracting domain logic into new modular crates (shipper-auth, shipper-cargo-failure, shipper-chunking), refactoring shipper-cli to use external progress reporting, adding multi-registry publishing support, expanding CI/mutation testing automation, and introducing comprehensive BDD test coverage for CLI workflows.

Changes

Cohort / File(s) Summary
Release & Configuration
.cargo/mutants.toml, CHANGELOG.md, RELEASE_CHECKLIST_v0.3.0-rc.1.md, RELEASE_NOTES_v0.3.0.md, ROADMAP.md
Added mutation testing config, release checklist, release notes for v0.3.0-rc.1, and updated roadmap with new version and features (multi-registry, sparse index caching, selective resumability).
GitHub Actions Workflows
.github/workflows/ci.yml, .github/workflows/fuzz.yml, .github/workflows/mutation.yml, .github/workflows/release.yml
Upgraded action versions (cache v4→v5, upload-artifact v4→v6, download-artifact v4→v7), added new mutation testing workflow, added fuzz targets (policy_effects, redact_output), introduced workflow concurrency configuration.
Workspace Root Config
Cargo.toml, .gitignore, README.md
Bumped workspace version to 0.3.0-rc.1, added six new workspace members (shipper-engine-parallel, shipper-chunking, shipper-output-sanitizer, shipper-levels, shipper-execution-core, shipper-config-runtime), added insta dev dependency; expanded .gitignore for test artifacts; expanded README with CI snippets, retry options, multi-registry and parallel publishing examples.
New Crate: shipper-auth
crates/shipper-auth/CLAUDE.md, crates/shipper-auth/Cargo.toml, crates/shipper-auth/README.md, crates/shipper-auth/src/lib.rs, crates/shipper-auth/proptest-regressions/lib.txt
New crate for authentication handling; added public function is_trusted_publishing_available() to detect GitHub Actions OIDC tokens; includes proptest regression seeds and comprehensive test suite for authentication logic.
New Crate: shipper-cargo-failure
crates/shipper-cargo-failure/CLAUDE.md, crates/shipper-cargo-failure/Cargo.toml, crates/shipper-cargo-failure/README.md, crates/shipper-cargo-failure/src/lib.rs
New crate for classifying cargo publish failures; expanded RETRYABLE_PATTERNS (16→20 entries) and PERMANENT_PATTERNS (17→22 entries) with new error patterns; added extensive snapshot and property-based tests.
New Crate: shipper-chunking
crates/shipper-chunking/CLAUDE.md, crates/shipper-chunking/Cargo.toml, crates/shipper-chunking/README.md, crates/shipper-chunking/src/lib.rs, crates/shipper-chunking/tests/*
New crate providing chunk_by_max_concurrent() function for bounded-concurrency batching; includes BDD and integration tests validating contiguous, order-preserving partitioning behavior.
Refactored Crate: shipper-cargo
crates/shipper-cargo/CLAUDE.md, crates/shipper-cargo/Cargo.toml, crates/shipper-cargo/README.md, crates/shipper-cargo/src/lib.rs, crates/shipper-cargo/tests/*
Refactored to externalize redaction and tail-lines logic to shipper-output-sanitizer; added extensive integration tests (cargo_commands.rs) and contract tests (redaction_contract.rs); newly exported redact_sensitive in public API.
Refactored CLI: shipper-cli
crates/shipper-cli/CLAUDE.md, crates/shipper-cli/Cargo.toml, crates/shipper-cli/README.md, crates/shipper-cli/src/main.rs, crates/shipper-cli/tests/*
Removed local progress.rs module; integrated external shipper-progress crate; added multi-registry publishing support with per-registry state/options; added resume_from and quiet CLI options; refactored policy/verify/readiness parsing to use config module types; enhanced doctor diagnostics and preflight output; added 2000+ lines of BDD test coverage across 11 test files (config, doctor, error handling, error recovery, micro backends, parallel, preflight, publish, publish_advanced, publish_flow, resume, status, workflow, CLI snapshots, e2e tests).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Modular dreams take shape so fine,
New crates split logic, borders align,
From CLI's clutch, progress flies free,
Registries many, all in harmony,
Tests blossom forth—v0.3 awaits with glee! 🌾

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Release v0.3.0-rc.1' clearly and concisely describes the main objective of the changeset—preparing and releasing version 0.3.0-rc.1.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset, covering key features, maintenance work, and quality assurance updates for the v0.3.0-rc.1 release.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch release/v0.3.0-rc.1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

EffortlessSteven and others added 3 commits February 27, 2026 21:58
- Fully addresses remaining Dependabot PR.
Add extensive test coverage across all 31 workspace microcrates:

Unit tests:
- shipper-auth: 41 tests (token resolution, credentials, priority ordering)
- shipper-encrypt: 58 tests (roundtrips, corruption, property tests)
- shipper-events: 50 tests (serialization, JSONL, filtering, property tests)
- shipper-git: 49 tests (status, branches, tags, context, serialization)
- shipper-levels: 26 tests (computation, DAGs, cycles, property tests)
- shipper-lock: 29 tests (acquisition, contention, cleanup)
- shipper-plan: 37 tests (building, ordering, selection, snapshots)
- shipper-process: 44 tests (execution, timeouts, output capture)
- shipper-webhook: 45 tests (payloads, HMAC, delivery, mock server)
- shipper-environment: 69 tests (CI detection, fingerprints, versions)
- shipper-registry: 58 tests (HTTP mocking, URLs, owners, sparse index)
- shipper-store: 47 tests (persistence, receipts, corruption)
- shipper-state: 25 integration tests (atomic writes, migration)
- shipper-storage: 33 integration tests (file ops, backends)

Snapshot tests (insta):
- shipper-config: 10 snapshots (defaults, validation errors, roundtrips)
- shipper-types: 5 snapshots (plans, states, receipts, reports)
- shipper-plan: 7 snapshots (plans, errors, selections)
- shipper-cli: 14 snapshots (help texts, version, errors)

Property-based tests (proptest):
- shipper-config: 5 property tests (roundtrips, validation, merge)
- shipper-types: 27 property tests (all type roundtrips, transitions)
- shipper-events: 11 property tests (serialization, ordering)
- shipper-registry: 6 property tests (URLs, sparse index, roundtrips)

BDD tests:
- bdd_doctor: 5 tests (environment, cargo, token, workspace)
- bdd_publish_flow: 12 tests (preview, errors, filtering, state)
- bdd_resume: 4 tests (no state, empty, corrupt, missing dir)

E2E tests:
- e2e_plan: 9 tests (workspaces, filtering, determinism, ordering)
- e2e_config: 8 tests (init, validate, flags, errors)
- e2e_preflight: 14 tests (clean, git, ownership, JSON, events)
- e2e_status: 10 tests (published/missing, filtering, format)

Doc tests:
- 12 doc-test examples across 6 crates (duration, retry, chunking,
  output-sanitizer, policy, schema)

Fuzz targets:
- webhook_payload: payload deserialization, HMAC, URL parsing
- plan_builder: workspace manifests, topo sort verification
- git_context: context deserialization, commit parsing

Infrastructure:
- .github/workflows/mutation.yml: weekly cargo-mutants CI
- .cargo/mutants.toml: mutation testing configuration
- Fixed clippy warnings across workspace

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: Lint (fmt + clippy)

Failed stage: Clippy [❌]

Failed test name: bdd_publish

Failure summary:

The GitHub Action failed during cargo clippy --workspace --all-targets --all-features -- -D warnings
because the crate shipper-cli could not compile its test target bdd_publish.
- Rust compiler error
E0382 occurred in crates/shipper-cli/tests/bdd_publish.rs:215:9: path (a std::path::PathBuf) was
moved when passed to fs::set_permissions(path, perms) at line 214, then used again at line 215.
-
This is a move/borrow issue; fs::set_permissions should receive &path (or path.clone()) if path must
be returned/used afterward.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

163:  RUSTFLAGS: -Dwarnings
164:  targets: 
165:  components: rustfmt, clippy
166:  ##[endgroup]
167:  ##[group]Run : set $CARGO_HOME
168:  �[36;1m: set $CARGO_HOME�[0m
169:  �[36;1mecho CARGO_HOME=${CARGO_HOME:-"$HOME/.cargo"} >> $GITHUB_ENV�[0m
170:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
171:  env:
172:  CARGO_TERM_COLOR: always
173:  RUSTFLAGS: -Dwarnings
174:  ##[endgroup]
175:  ##[group]Run : install rustup if needed
176:  �[36;1m: install rustup if needed�[0m
177:  �[36;1mif ! command -v rustup &>/dev/null; then�[0m
178:  �[36;1m  curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail https://sh.rustup.rs | sh -s -- --default-toolchain none -y�[0m
179:  �[36;1m  echo "$CARGO_HOME/bin" >> $GITHUB_PATH�[0m
...

250:  �[36;1m  if rustc +stable --version --verbose | grep -q '^release: 1\.6[89]\.'; then�[0m
251:  �[36;1m    touch "/home/runner/work/_temp"/.implicit_cargo_registries_crates_io_protocol || true�[0m
252:  �[36;1m    echo CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse >> $GITHUB_ENV�[0m
253:  �[36;1m  elif rustc +stable --version --verbose | grep -q '^release: 1\.6[67]\.'; then�[0m
254:  �[36;1m    touch "/home/runner/work/_temp"/.implicit_cargo_registries_crates_io_protocol || true�[0m
255:  �[36;1m    echo CARGO_REGISTRIES_CRATES_IO_PROTOCOL=git >> $GITHUB_ENV�[0m
256:  �[36;1m  fi�[0m
257:  �[36;1mfi�[0m
258:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
259:  env:
260:  CARGO_TERM_COLOR: always
261:  RUSTFLAGS: -Dwarnings
262:  CARGO_HOME: /home/runner/.cargo
263:  CARGO_INCREMENTAL: 0
264:  ##[endgroup]
265:  ##[group]Run : work around spurious network errors in curl 8.0
266:  �[36;1m: work around spurious network errors in curl 8.0�[0m
267:  �[36;1m# https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/timeout.20investigation�[0m
...

327:  CARGO_INCREMENTAL: 0
328:  ##[endgroup]
329:  ##[group]Run cargo clippy --workspace --all-targets --all-features -- -D warnings
330:  �[36;1mcargo clippy --workspace --all-targets --all-features -- -D warnings�[0m
331:  shell: /usr/bin/bash -e {0}
332:  env:
333:  CARGO_TERM_COLOR: always
334:  RUSTFLAGS: -Dwarnings
335:  CARGO_HOME: /home/runner/.cargo
336:  CARGO_INCREMENTAL: 0
337:  ##[endgroup]
338:  �[1m�[92m Downloading�[0m crates ...
339:  �[1m�[92m  Downloaded�[0m bit-set v0.8.0
340:  �[1m�[92m  Downloaded�[0m fastrand v2.3.0
341:  �[1m�[92m  Downloaded�[0m difflib v0.4.0
342:  �[1m�[92m  Downloaded�[0m quick-error v1.2.3
343:  �[1m�[92m  Downloaded�[0m wait-timeout v0.2.1
...

380:  �[1m�[92m  Downloaded�[0m chunked_transfer v1.5.0
381:  �[1m�[92m   Compiling�[0m num-traits v0.2.19
382:  �[1m�[92m   Compiling�[0m rustix v1.1.4
383:  �[1m�[92m    Checking�[0m fastrand v2.3.0
384:  �[1m�[92m    Checking�[0m regex-syntax v0.8.10
385:  �[1m�[92m    Checking�[0m wait-timeout v0.2.1
386:  �[1m�[92m    Checking�[0m shipper-retry v0.3.0-rc.1 (/home/runner/work/shipper/shipper/crates/shipper-retry)
387:  �[1m�[92m    Checking�[0m shipper-duration v0.3.0-rc.1 (/home/runner/work/shipper/shipper/crates/shipper-duration)
388:  �[1m�[92m    Checking�[0m shipper-encrypt v0.3.0-rc.1 (/home/runner/work/shipper/shipper/crates/shipper-encrypt)
389:  �[1m�[92m    Checking�[0m shipper-webhook v0.3.0-rc.1 (/home/runner/work/shipper/shipper/crates/shipper-webhook)
390:  �[1m�[92m    Checking�[0m shipper-levels v0.3.0-rc.1 (/home/runner/work/shipper/shipper/crates/shipper-levels)
391:  �[1m�[92m   Compiling�[0m getrandom v0.3.4
392:  �[1m�[92m   Compiling�[0m zerocopy v0.8.40
393:  �[1m�[92m    Checking�[0m rand_core v0.9.5
394:  �[1m�[92m    Checking�[0m bit-vec v0.8.0
395:  �[1m�[92m    Checking�[0m quick-error v1.2.3
396:  �[1m�[92m    Checking�[0m bit-set v0.8.0
...

451:  �[1m�[92m    Checking�[0m shipper-progress v0.3.0-rc.1 (/home/runner/work/shipper/shipper/crates/shipper-progress)
452:  �[1m�[92m    Checking�[0m float-cmp v0.10.0
453:  �[1m�[92m    Checking�[0m difflib v0.4.0
454:  �[1m�[92m   Compiling�[0m assert_cmd v2.1.2
455:  �[1m�[92m    Checking�[0m normalize-line-endings v0.3.0
456:  �[1m�[92m    Checking�[0m termtree v0.5.1
457:  �[1m�[92m    Checking�[0m predicates-tree v1.0.13
458:  �[1m�[92m    Checking�[0m ryu v1.0.23
459:  �[1m�[92m    Checking�[0m unsafe-libyaml v0.2.11
460:  �[1m�[92m    Checking�[0m regex-automata v0.4.14
461:  �[1m�[92m    Checking�[0m shipper-cli v0.3.0-rc.1 (/home/runner/work/shipper/shipper/crates/shipper-cli)
462:  �[1m�[92m    Checking�[0m serde_yaml v0.9.34+deprecated
463:  �[1m�[92m    Checking�[0m regex v1.12.3
464:  �[1m�[92m    Checking�[0m bstr v1.12.1
465:  �[1m�[92m    Checking�[0m predicates v3.1.4
466:  �[1m�[91merror[E0382]�[0m�[1m: use of moved value: `path`�[0m
467:  �[1m�[94m--> �[0mcrates/shipper-cli/tests/bdd_publish.rs:215:9
468:  �[1m�[94m|�[0m
469:  �[1m�[94m206�[0m �[1m�[94m|�[0m         let path = bin_dir.join("cargo");
470:  �[1m�[94m|�[0m             �[1m�[94m----�[0m �[1m�[94mmove occurs because `path` has type `std::path::PathBuf`, which does not implement the `Copy` trait�[0m
471:  �[1m�[94m...�[0m
472:  �[1m�[94m214�[0m �[1m�[94m|�[0m         fs::set_permissions(path, perms).expect("chmod");
473:  �[1m�[94m|�[0m                             �[1m�[94m----�[0m �[1m�[94mvalue moved here�[0m
474:  �[1m�[94m215�[0m �[1m�[94m|�[0m         path
475:  �[1m�[94m|�[0m         �[1m�[91m^^^^�[0m �[1m�[91mvalue used here after move�[0m
476:  �[1m�[94m|�[0m
477:  �[1m�[96mhelp�[0m: consider borrowing `path`
478:  �[1m�[94m|�[0m
479:  �[1m�[94m214�[0m �[1m�[94m| �[0m        fs::set_permissions(�[92m&�[0mpath, perms).expect("chmod");
480:  �[1m�[94m|�[0m                             �[92m+�[0m
481:  �[1mFor more information about this error, try `rustc --explain E0382`.�[0m
482:  �[1m�[91merror�[0m: could not compile `shipper-cli` (test "bdd_publish") due to 1 previous error
483:  �[1m�[33mwarning�[0m: build failed, waiting for other jobs to finish...
484:  ##[error]Process completed with exit code 101.
485:  Post job cleanup.

EffortlessSteven and others added 2 commits February 28, 2026 18:33
- Add 22 unit tests for shipper-progress
- Add 38 unit tests + 5 proptests for shipper-execution-core
- Add 53 tests for shipper-cargo (unit + integration)
- Add 88 tests + 5 proptests for shipper-cargo-failure
- Add 47 contract tests for shipper-config-runtime
- Add 14 tests for shipper-engine-parallel
- Add 11 cross-crate integration tests for shipper facade
- Add 8 BDD parallel publishing tests
- Add README.md for 29 microcrates (crates.io display)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Property tests added (proptest):
- shipper-auth: 8 property tests (token roundtrip, env resolution, precedence)
- shipper-cargo: 13 property tests (CargoOutput, package validation, tail_lines)
- shipper-cargo-failure: 5 property tests (classification roundtrip)
- shipper-environment: 14 property tests (CI detection, fingerprint, serde)
- shipper-git: 12 property tests (context fields, status parsing, serde)
- shipper-process: 10 property tests (output, exit codes, command building)
- shipper-state: 6 property tests (state/receipt roundtrip, save/load)
- shipper-storage: 6 property tests (paths, atomic write, nested dirs)
- shipper-store: 4 property tests (receipt roundtrip, events append)
- shipper-webhook: 10 property tests (payload roundtrip, signatures)

Snapshot tests added (insta):
- shipper-auth: 14 snapshots (token resolution, errors, credentials)
- shipper-cargo-failure: 12 snapshots (classification, formatting, debug)
- shipper-events: 13 snapshots (event serialization, log format)
- shipper-registry: 16 snapshots (responses, URLs, sparse index, errors)
- shipper-state: 5 snapshots (state/receipt JSON, transitions)

BDD tests added:
- bdd_preflight.rs: 7 tests covering all preflight_checks.feature scenarios
- bdd_micro_backends.rs: 2 tests for micro_backend_feature_flags.feature
- bdd_error_recovery.rs: 18 tests for error recovery and retry scenarios

Integration tests:
- facade_integration.rs: 23 tests (plan, config, state, events, auth, registry)

Bug fixes:
- Fix use-after-move in fs::set_permissions across 8 test files (Linux CI)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 1, 2026 00:13

Copilot AI 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.

Pull request overview

Prepares the workspace for the v0.3.0-rc.1 release candidate by introducing new microcrates, expanding test coverage (BDD/e2e/snapshots), updating documentation/release artifacts, and refreshing CI workflows for the new architecture.

Changes:

  • Add new microcrates (e.g., shipper-execution-core, shipper-chunking, shipper-config-runtime, shipper-engine-parallel) and wire them into the workspace.
  • Expand automated coverage via new BDD/e2e tests, proptest regressions, and snapshot baselines across several crates.
  • Update release docs/changelog and refresh GitHub Actions workflows (including adding mutation testing).

Reviewed changes

Copilot reviewed 145 out of 283 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
crates/shipper-levels/README.md Adds crate README for the levels microcrate.
crates/shipper-git/README.md Adds crate README for git operations microcrate.
crates/shipper-git/Cargo.toml Adds dev-deps for property/snapshot-style testing.
crates/shipper-execution-core/tests/execution_core_bdd.rs Adds BDD tests covering state updates/persistence.
crates/shipper-execution-core/README.md Adds crate README for execution-core microcrate.
crates/shipper-execution-core/Cargo.toml Introduces new microcrate manifest + deps/dev-deps.
crates/shipper-events/src/snapshots/shipper_events__tests__readiness_started_yaml.snap Adds insta snapshot baseline for events.
crates/shipper-events/src/snapshots/shipper_events__tests__readiness_complete_yaml.snap Adds insta snapshot baseline for events.
crates/shipper-events/src/snapshots/shipper_events__tests__preflight_ownership_check_yaml.snap Adds insta snapshot baseline for events.
crates/shipper-events/src/snapshots/shipper_events__tests__preflight_complete_yaml.snap Adds insta snapshot baseline for events.
crates/shipper-events/src/snapshots/shipper_events__tests__plan_created_yaml.snap Adds insta snapshot baseline for events.
crates/shipper-events/src/snapshots/shipper_events__tests__package_started_yaml.snap Adds insta snapshot baseline for events.
crates/shipper-events/src/snapshots/shipper_events__tests__package_started_json.snap Adds insta snapshot baseline for events.
crates/shipper-events/src/snapshots/shipper_events__tests__package_skipped_yaml.snap Adds insta snapshot baseline for events.
crates/shipper-events/src/snapshots/shipper_events__tests__package_published_yaml.snap Adds insta snapshot baseline for events.
crates/shipper-events/src/snapshots/shipper_events__tests__package_failed_retryable_yaml.snap Adds insta snapshot baseline for events.
crates/shipper-events/src/snapshots/shipper_events__tests__multiple_events_jsonl.snap Adds insta snapshot baseline for jsonl output.
crates/shipper-events/src/snapshots/shipper_events__tests__execution_finished_success_yaml.snap Adds insta snapshot baseline for events.
crates/shipper-events/src/snapshots/shipper_events__tests__event_log_package_lifecycle.snap Adds insta snapshot baseline for event log aggregation.
crates/shipper-events/README.md Adds crate README for events microcrate.
crates/shipper-events/Cargo.toml Adds dev-deps for snapshot/property testing.
crates/shipper-environment/proptest-regressions/lib.txt Checks in proptest regression seeds for stability.
crates/shipper-environment/README.md Adds crate README for environment microcrate.
crates/shipper-environment/Cargo.toml Adds dev-deps for serial/env/property testing.
crates/shipper-engine-parallel/tests/chunking_integration.rs Adds integration test for chunking order preservation.
crates/shipper-engine-parallel/tests/chunking_bdd.rs Adds BDD tests for chunking behavior.
crates/shipper-engine-parallel/src/webhook.rs Refines docs/attributes and removes async thread-spawn API.
crates/shipper-engine-parallel/README.md Adds crate README for parallel engine microcrate.
crates/shipper-engine-parallel/Cargo.toml Introduces new crate manifest + deps/dev-deps for parallel engine.
crates/shipper-encrypt/README.md Adds crate README for encryption microcrate.
crates/shipper-encrypt/Cargo.toml Adds dev-deps for property tests and serde_json.
crates/shipper-duration/src/lib.rs Adds rustdoc examples for duration parsing.
crates/shipper-duration/README.md Adds crate README for duration microcrate.
crates/shipper-duration/Cargo.toml Bumps toml dev-dependency major version.
crates/shipper-config/tests/config_integration.rs Adds integration tests for config template loading/merging.
crates/shipper-config/src/snapshots/shipper_config__tests__snapshot_tests__validation_error_zero_output_lines.snap Adds validation error insta snapshot.
crates/shipper-config/src/snapshots/shipper_config__tests__snapshot_tests__validation_error_zero_max_attempts.snap Adds validation error insta snapshot.
crates/shipper-config/src/snapshots/shipper_config__tests__snapshot_tests__validation_error_zero_base_delay.snap Adds validation error insta snapshot.
crates/shipper-config/src/snapshots/shipper_config__tests__snapshot_tests__validation_error_max_delay_lt_base.snap Adds validation error insta snapshot.
crates/shipper-config/src/snapshots/shipper_config__tests__snapshot_tests__validation_error_jitter_out_of_range.snap Adds validation error insta snapshot.
crates/shipper-config/src/snapshots/shipper_config__tests__snapshot_tests__validation_error_empty_registry_name.snap Adds validation error insta snapshot.
crates/shipper-config/src/snapshots/shipper_config__tests__snapshot_tests__toml_roundtrip_parsed.snap Adds roundtrip parse insta snapshot.
crates/shipper-config/src/snapshots/shipper_config__tests__snapshot_tests__default_toml_template.snap Adds default template insta snapshot.
crates/shipper-config/src/snapshots/shipper_config__tests__snapshot_tests__default_config.snap Adds default config insta snapshot.
crates/shipper-config/src/snapshots/shipper_config__tests__snapshot_tests__config_all_fields.snap Adds full config insta snapshot.
crates/shipper-config/README.md Adds crate README for config microcrate.
crates/shipper-config/Cargo.toml Bumps toml and adds schema + testing deps.
crates/shipper-config-runtime/tests/config_runtime_bdd.rs Adds BDD tests for config→types conversion behavior.
crates/shipper-config-runtime/src/lib.rs Introduces conversion layer crate + unit/proptest coverage.
crates/shipper-config-runtime/README.md Adds crate README for config-runtime microcrate.
crates/shipper-config-runtime/Cargo.toml Introduces new crate manifest + deps.
crates/shipper-cli/tests/snapshots/cli_snapshots__version_flag.snap Adds CLI snapshot for version output.
crates/shipper-cli/tests/snapshots/cli_snapshots__unknown_subcommand_error.snap Adds CLI snapshot for error output.
crates/shipper-cli/tests/snapshots/cli_snapshots__status_help.snap Adds CLI snapshot for status help text.
crates/shipper-cli/tests/snapshots/cli_snapshots__resume_help.snap Adds CLI snapshot for resume help text.
crates/shipper-cli/tests/snapshots/cli_snapshots__publish_help.snap Adds CLI snapshot for publish help text.
crates/shipper-cli/tests/snapshots/cli_snapshots__preflight_help.snap Adds CLI snapshot for preflight help text.
crates/shipper-cli/tests/snapshots/cli_snapshots__plan_help.snap Adds CLI snapshot for plan help text.
crates/shipper-cli/tests/snapshots/cli_snapshots__no_subcommand_error.snap Adds CLI snapshot for missing-subcommand error.
crates/shipper-cli/tests/snapshots/cli_snapshots__help_text.snap Adds CLI snapshot for top-level help.
crates/shipper-cli/tests/snapshots/cli_snapshots__doctor_help.snap Adds CLI snapshot for doctor help text.
crates/shipper-cli/tests/snapshots/cli_snapshots__config_help.snap Adds CLI snapshot for config help text.
crates/shipper-cli/tests/snapshots/cli_snapshots__completion_missing_shell.snap Adds CLI snapshot for completion arg error.
crates/shipper-cli/tests/snapshots/cli_snapshots__clean_help.snap Adds CLI snapshot for clean help text.
crates/shipper-cli/tests/snapshots/cli_snapshots__ci_help.snap Adds CLI snapshot for ci help text.
crates/shipper-cli/tests/e2e_status.rs Adds end-to-end tests for shipper status using mock HTTP server.
crates/shipper-cli/tests/e2e_plan.rs Adds end-to-end tests for shipper plan output/ordering.
crates/shipper-cli/tests/e2e_config.rs Adds end-to-end tests for shipper config init/validate.
crates/shipper-cli/tests/cli_snapshots.rs Adds insta snapshot harness for CLI help/error outputs.
crates/shipper-cli/tests/cli_e2e.rs Fixes permissions call to use &path.
crates/shipper-cli/tests/bdd_resume.rs Adds BDD coverage for resume failure scenarios.
crates/shipper-cli/tests/bdd_publish.rs Expands publish BDD coverage including redaction + policy behavior.
crates/shipper-cli/tests/bdd_preflight.rs Adds BDD coverage for preflight workflows.
crates/shipper-cli/tests/bdd_micro_backends.rs Adds BDD coverage verifying micro backend stability.
crates/shipper-cli/tests/bdd_doctor.rs Adds BDD coverage for doctor diagnostics flows.
crates/shipper-cli/src/progress.rs Removes in-crate progress reporting implementation.
crates/shipper-cli/Cargo.toml Bumps shipper dependency version, adds shipper-progress, enables micro-all by default.
crates/shipper-chunking/tests/chunking_contract_integration.rs Adds integration test for chunking on structured items.
crates/shipper-chunking/tests/chunking_bdd.rs Adds BDD tests for chunking edge cases.
crates/shipper-chunking/src/lib.rs Introduces chunking microcrate implementation + tests/proptests.
crates/shipper-chunking/README.md Adds crate README for chunking microcrate.
crates/shipper-chunking/Cargo.toml Introduces new crate manifest + dev-deps.
crates/shipper-cargo/tests/redaction_contract.rs Adds contract test aligning redaction behavior across crates.
crates/shipper-cargo/tests/cargo_commands.rs Adds broad integration tests for cargo command execution helpers.
crates/shipper-cargo/README.md Adds crate README for cargo microcrate.
crates/shipper-cargo/Cargo.toml Pulls in output sanitizer dependency and test deps.
crates/shipper-cargo-failure/src/snapshots/shipper_cargo_failure__tests__retryable_precedence.snap Adds classification snapshot baseline.
crates/shipper-cargo-failure/src/snapshots/shipper_cargo_failure__tests__retryable_classification.snap Adds classification snapshot baseline.
crates/shipper-cargo-failure/src/snapshots/shipper_cargo_failure__tests__realistic_rate_limit.snap Adds classification snapshot baseline.
crates/shipper-cargo-failure/src/snapshots/shipper_cargo_failure__tests__realistic_compilation_failure.snap Adds classification snapshot baseline.
crates/shipper-cargo-failure/src/snapshots/shipper_cargo_failure__tests__realistic_already_published.snap Adds classification snapshot baseline.
crates/shipper-cargo-failure/src/snapshots/shipper_cargo_failure__tests__permanent_classification.snap Adds classification snapshot baseline.
crates/shipper-cargo-failure/src/snapshots/shipper_cargo_failure__tests__debug_retryable.snap Adds debug-format snapshot baseline.
crates/shipper-cargo-failure/src/snapshots/shipper_cargo_failure__tests__debug_permanent.snap Adds debug-format snapshot baseline.
crates/shipper-cargo-failure/src/snapshots/shipper_cargo_failure__tests__debug_class_retryable.snap Adds debug-format snapshot baseline.
crates/shipper-cargo-failure/src/snapshots/shipper_cargo_failure__tests__debug_class_permanent.snap Adds debug-format snapshot baseline.
crates/shipper-cargo-failure/src/snapshots/shipper_cargo_failure__tests__debug_class_ambiguous.snap Adds debug-format snapshot baseline.
crates/shipper-cargo-failure/src/snapshots/shipper_cargo_failure__tests__debug_ambiguous.snap Adds debug-format snapshot baseline.
crates/shipper-cargo-failure/src/snapshots/shipper_cargo_failure__tests__ambiguous_classification.snap Adds classification snapshot baseline.
crates/shipper-cargo-failure/src/snapshots/shipper_cargo_failure__tests__all_messages.snap Adds snapshot baseline for message set.
crates/shipper-cargo-failure/README.md Adds crate README for cargo-failure microcrate.
crates/shipper-cargo-failure/Cargo.toml Adds insta dev-dependency.
crates/shipper-auth/src/snapshots/shipper_auth__tests__snapshots__snapshot_resolve_token_none_found.snap Adds auth snapshot baseline.
crates/shipper-auth/src/snapshots/shipper_auth__tests__snapshots__snapshot_resolve_token_from_env_registry.snap Adds auth snapshot baseline.
crates/shipper-auth/src/snapshots/shipper_auth__tests__snapshots__snapshot_resolve_token_from_env_default.snap Adds auth snapshot baseline.
crates/shipper-auth/src/snapshots/shipper_auth__tests__snapshots__snapshot_resolve_token_from_credentials_file.snap Adds auth snapshot baseline.
crates/shipper-auth/src/snapshots/shipper_auth__tests__snapshots__snapshot_resolve_token_custom_registry_from_credentials.snap Adds auth snapshot baseline.
crates/shipper-auth/src/snapshots/shipper_auth__tests__snapshots__snapshot_list_configured_registries_mixed.snap Adds auth snapshot baseline.
crates/shipper-auth/src/snapshots/shipper_auth__tests__snapshots__snapshot_list_configured_registries_empty.snap Adds auth snapshot baseline.
crates/shipper-auth/src/snapshots/shipper_auth__tests__snapshots__snapshot_error_token_not_found_for_registry.snap Adds auth error snapshot baseline.
crates/shipper-auth/src/snapshots/shipper_auth__tests__snapshots__snapshot_error_missing_credentials_file.snap Adds auth error snapshot baseline.
crates/shipper-auth/src/snapshots/shipper_auth__tests__snapshots__snapshot_error_malformed_credentials_file.snap Adds auth error snapshot baseline.
crates/shipper-auth/src/snapshots/shipper_auth__tests__snapshots__snapshot_credentials_legacy_toplevel_format.snap Adds credentials parsing snapshot baseline.
crates/shipper-auth/src/snapshots/shipper_auth__tests__snapshots__snapshot_credentials_custom_registry_section.snap Adds credentials parsing snapshot baseline.
crates/shipper-auth/src/snapshots/shipper_auth__tests__snapshots__snapshot_credentials_crates_io_registry_section.snap Adds credentials parsing snapshot baseline.
crates/shipper-auth/src/snapshots/shipper_auth__tests__snapshots__snapshot_auth_info_default.snap Adds default auth snapshot baseline.
crates/shipper-auth/proptest-regressions/lib.txt Checks in proptest regression seed for auth.
crates/shipper-auth/README.md Adds crate README for auth microcrate.
crates/shipper-auth/Cargo.toml Bumps toml/dirs and adds testing deps.
ROADMAP.md Updates roadmap current release pointer to 0.3.0-rc.1.
RELEASE_NOTES_v0.3.0.md Adds release notes content for 0.3.0-rc.1.
RELEASE_CHECKLIST_v0.3.0.md Adds release checklist for 0.3.0-rc.1.
README.md Updates top-level docs for new commands/features and multi-registry.
Cargo.toml Bumps workspace version to 0.3.0-rc.1, adds new members, adds workspace dev-deps.
CHANGELOG.md Adds 0.3.0-rc.1 changelog entry.
.github/workflows/release.yml Updates workflow action versions related to releases/artifacts.
.github/workflows/mutation.yml Adds scheduled/manual mutation testing workflow.
.github/workflows/fuzz.yml Updates fuzz targets and workflow action versions.
.github/workflows/ci.yml Updates CI action versions and expands feature-matrix.
.cargo/mutants.toml Adds cargo-mutants configuration.

- [x] Verify `Cargo.toml` workspace version is `0.3.0-rc.1`
- [x] Update `CHANGELOG.md` with 0.3.0-rc.1 entry
- [x] Update `ROADMAP.md` current version
- [x] Create `RELEASE_NOTES_v0.3.0-rc.1.md`

Copilot AI Mar 1, 2026

Copy link

Choose a reason for hiding this comment

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

The checklist references RELEASE_NOTES_v0.3.0-rc.1.md, but this PR adds RELEASE_NOTES_v0.3.0.md (with v0.3.0-rc.1 content). Align the filename in the checklist (or rename the release notes file) so release automation/manual steps are unambiguous.

Suggested change
- [x] Create `RELEASE_NOTES_v0.3.0-rc.1.md`
- [x] Create `RELEASE_NOTES_v0.3.0.md`

Copilot uses AI. Check for mistakes.

/// Split a list of items into contiguous chunks bounded by `max_concurrent`.
///
/// - `max_concurrent <= 0` is treated as `1`.

Copilot AI Mar 1, 2026

Copy link

Choose a reason for hiding this comment

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

The docs say max_concurrent <= 0, but the parameter type is usize, so the only non-positive value representable is 0. Consider updating the wording to max_concurrent == 0 (or change the API type if you truly want to accept negatives).

Suggested change
/// - `max_concurrent <= 0` is treated as `1`.
/// - If `max_concurrent == 0`, a batch size of `1` is used.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +45
for idx in 0..expected_requests {
let req = server.recv().expect("request");
let status = statuses
.get(idx)
.copied()
.or_else(|| statuses.last().copied())
.unwrap_or(404);

Copilot AI Mar 1, 2026

Copy link

Choose a reason for hiding this comment

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

The mock registry thread uses a blocking server.recv(), and the test unconditionally join()s the thread. If the CLI makes fewer requests than expected_requests (e.g., early exit on failure), this will block forever and hang the test suite. Use recv_timeout(...) (with a short timeout) and exit the loop on timeout/error to keep tests fail-fast and non-hanging.

Copilot uses AI. Check for mistakes.
Comment on lines +273 to 277
for _ in 0..10 {
let req = match server.recv_timeout(Duration::from_secs(60)) {
Ok(Some(req)) => req,
_ => break,
};

Copilot AI Mar 1, 2026

Copy link

Choose a reason for hiding this comment

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

This increases the maximum wait for readiness mock synchronization to 60s per test run attempt. Since the loop breaks on the first timeout, a missing request now costs up to 60 seconds per failing/flaky run, which can significantly slow CI feedback. Consider lowering the timeout (e.g., 5–10s) and/or making it configurable so failures remain quick while still being robust.

Copilot uses AI. Check for mistakes.
EffortlessSteven and others added 9 commits February 28, 2026 19:27
Add comprehensive E2E tests for publish flow (8 tests) and doctor
command (7 tests). Add BDD-style tests for status command (10 tests)
and config init/validate commands (7 tests). Add 3 new fuzz targets
for config_parse, state_load, and receipt_load.

Test coverage: +32 E2E/BDD tests, +3 fuzz targets

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Property tests: levels (9), encrypt (8), progress (7), duration (9), lock (5)
Snapshot tests: config (5), environment (10), git (17), lock (15), plan (3),
  process (12), storage (21), store (23), webhook (16)
Integration tests: plan→engine (9), config→CLI (10)
Documentation: docs/testing.md, docs/architecture.md

Test coverage: +38 proptests, +122 snapshots, +19 integration tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add 4 new feature files:
- status_command.feature (8 scenarios): version comparison, registry status
- config_management.feature (12 scenarios): init, validate, merge, precedence
- resume_workflow.feature (9 scenarios): resume partial publishes, plan_id checks
- error_handling.feature (13 scenarios): auth failures, network, rate limiting

Add scenarios to existing feature files:
- preflight_checks.feature: +4 scenarios (allow-dirty, finishability, ownership skip, ordering)
- parallel_levels.feature: +5 scenarios (independent, linear, single, diamond, concurrency limit)
- publish_resume.feature: +4 scenarios (events log, custom state dir, no-verify, failure receipt)
- micro_backend_feature_flags.feature: +2 scenarios (plan output, config loading)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Snapshot tests: types (22), cargo-failure (20)
Doc comments: shipper-plan, shipper-config, shipper-types, shipper-auth
Feature files: 4 new (status, config, resume, error_handling) + 4 expanded
CI: Add concurrency group with cancel-in-progress
Dependency: Merge dirs 5→6 (PR #16)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…optests, lock proptests

Proptests: execution-core (12), config-runtime (15), lock (12)
Snapshots: execution-core (21)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…, types

Snapshot tests: sparse-index (13), schema (17), policy (11), execution-core (21)
Property tests: schema (7), policy (13), sparse-index (17), types (13),
  execution-core (12), config-runtime (15), lock (12)
BDD feature files: status, config, resume, error handling (42 scenarios)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ocs update

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
EffortlessSteven and others added 2 commits March 1, 2026 11:11
- retry: +14 tests (41 total) — zero/overflow retries, jitter edges,
  backoff saturation, policy snapshots, proptest backoff cap
- chunking: +19 tests (43 total) — empty/single/large inputs,
  chunk boundaries, ordering preservation, proptest conservation
- engine-parallel: +13 tests (49 total) — zero/one/large concurrency,
  level ordering, diamond/linear snapshots, error propagation
- plan: +18 tests (58 total) — empty/linear/diamond/wide topologies,
  special names, stability (10 runs), proptest ordering + determinism
- progress: +23 tests (74 total) — zero/max totals, beyond-total,
  concurrent threads, percentage snapshots, status transitions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- auth: +45 tests (108 total) — credential precedence, malformed/unicode
  tokens, trusted publishing detection, OIDC, proptest robustness
- git: +37 tests (115 total) — empty/detached/no-commits repos, binary
  files, submodules, unicode filenames, long paths, proptest parsing
- events: +50 tests (114 total) — corrupt JSONL, large payloads, unicode,
  trailing newlines, all EventType roundtrips + snapshots, concurrent append
- lock: +42 tests (99 total) — stale recovery, concurrent acquire, force
  break, corrupt content, unicode paths, zero/max timeout, proptest pairing
- storage: +18 tests (105 total) — concurrent r/w, large files, unicode,
  deeply nested dirs, atomic write, proptest roundtrip

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@gitguardian

gitguardian Bot commented Mar 1, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 9 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
27721360 Triggered Basic Auth String a8d6133 crates/shipper-git/src/lib.rs View secret
27722904 Triggered Bearer Token 1ffd228 crates/shipper-output-sanitizer/src/lib.rs View secret
27725107 Triggered Generic Password d5ca640 crates/shipper-encrypt/src/lib.rs View secret
27725108 Triggered Generic Password d5ca640 crates/shipper-encrypt/src/lib.rs View secret
27725953 Triggered Generic Password 783fc50 crates/shipper/src/config.rs View secret
27725954 Triggered Bearer Token 3274118 crates/shipper/src/cargo.rs View secret
27725952 Triggered Generic High Entropy Secret 783fc50 crates/shipper/src/auth.rs View secret
27725955 Triggered Generic High Entropy Secret 783fc50 crates/shipper/src/auth.rs View secret
27728455 Triggered Generic Password f000312 crates/shipper-encrypt/src/lib.rs View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

EffortlessSteven and others added 18 commits March 1, 2026 12:02
…lippy warnings

- webhook: +39 tests (110 total) — malformed URLs, timeouts, large payloads, concurrent sends, HTTP status codes, snapshots
- execution-core: +44 tests (~127 total) — state machine transitions, concurrent updates, empty/single plans, snapshots, proptests, fp tolerance fix
- config: +69 tests (~110 total) — empty TOML, unknown sections, each section individually, policy presets, unicode, proptest roundtrip
- encrypt: +19 tests (105 total) — large data, key boundaries, tampered data, snapshots, proptests
- facade: +28 integration tests — deep chains, registry errors, state roundtrip, resume, event logs, receipts, lock lifecycle, snapshots
- Fix clippy: div_ceil in chunking, needless borrow in lock, unused mut in facade/cross_crate tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- types: +60 tests (153 total) — state transitions, plan determinism, receipt roundtrips, policy/mode exhaustive, snapshots, proptests
- process: +40 tests (106 total) — command building, output parsing, timeout, error classification, env passthrough, snapshots, proptests
- levels: +24 tests (64 total) — disconnected components, cycles, large graphs, stability, double diamond, snapshots, proptests
- sparse-index: +38 tests (62 total) — URL construction, crate name edges, response parsing, version filtering, snapshots, proptests
- CLI E2E: +26 tests — doctor, config init/validate, status, plan, clean, completion, CI templates, version, error output, snapshots

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- environment: +28 tests (121 total) — fingerprint completeness, OS/arch detection, Rust version parsing, cargo home, reproducibility, snapshots, proptests
- registry: +20 tests — HTTP error codes (429/500/502/503), malformed responses, version comparison, timeout, readiness edges, snapshots, proptests
- BDD: +10 workflow scenarios — resume, parallel publish, status, doctor diagnostics with full Given-When-Then step definitions
- fuzz: +3 new targets (receipt_deserialize, execution_state_deserialize, plan_levels)
- fix: restore mut lock for set_plan_id/release in facade/cross_crate tests with #[allow(unused_mut)] for all-features clippy

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…r(76), duration(73), schema(55)

- cargo-failure: +39 tests — real-world cargo errors (DNS, SSL, OOM, segfault, HTTP 409), ambiguous edge cases, cross-stream classification, 5 snapshots, 2 proptests
- config-runtime: +35 tests — flag precedence, default values, partial config, policy combinations, registry config, 6 snapshots, 2 proptests
- output-sanitizer: +26 tests — token patterns (JWT, basic auth, registry), no false positives, unicode, large output, 3 snapshots, 2 proptests, bug fix for empty line handling
- duration: +16 tests — zero forms, combined units, serde roundtrips, invalid inputs, 2 snapshots, 2 proptests
- schema: +17 tests — upgrade chains, downgrade rejection, unicode digits, edge cases, 2 snapshots, 3 proptests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- retry: +17 tests — backoff curves, strategy selection, jitter bounds, zero attempts, executor classification, 2 snapshots, 3 proptests
- policy: +23 tests — ordering invariants, field coverage, serialization, trait behavior, debug format, 4 snapshots, 4 proptests
- plan: +17 tests — build-dep ordering, multi-select, cycle detection, plan identity, dev-dep exclusion, determinism, 2 snapshots, 2 proptests
- state: +23 tests — persistence roundtrips, lifecycle transitions, plan ID validation, edge cases, 2 snapshots, 2 proptests
- store: +18 tests — directory creation, partial clear, custom state-dir, concurrent safety, 2 snapshots, 3 proptests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- auth: +21 tests — credentials parsing, token format, alternative registries, priority chain, 3 snapshots, 3 proptests
- git: +21 tests — status transitions, branch detection, detached HEAD, error handling, 3 snapshots, 2 proptests
- events: +16 tests — JSONL roundtrips, timestamp ordering, filtering, large logs, unicode, 2 snapshots, 2 proptests
- lock: +22 tests — acquisition, plan_id, stale lock, RAII drop, force acquire, 3 snapshots, 4 proptests
- storage: +22 tests — atomic writes, directory creation, roundtrips, error handling, concurrent access, 2 snapshots, 2 proptests
- fix: ignore concurrent_reads_and_writes_same_file on Windows (pre-existing rename race)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…g(65), encrypt + storage fix

- engine-parallel: +19 tests — concurrency control, dependency ordering, error propagation, progress tracking, 2 snapshots, 3 proptests
- webhook: +20 tests — payload construction, HTTP delivery, HMAC signatures, edge cases, 3 snapshots, 3 proptests
- progress: +17 tests — percentage precision, package tracking, display formatting, 2 snapshots, 2 proptests
- chunking: +15 tests — chunk boundaries, remainder handling, determinism, 2 snapshots, 3 proptests
- encrypt: additional hardening (agent in progress)
- storage: atomic write roundtrip snapshot fix

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…+5), encrypt hardening

- BDD: +7 scenarios — config validation, doctor diagnostics, clean command, plan filtering, preflight checks
- facade: +13 integration tests — error recovery, partial publish, plan filtering, readiness, events, receipts, locks
- CLI E2E: +22 snapshot tests — error output, help text, version, config, CI snippets, clean, plan, inspect
- fuzz: +5 new targets (config_deserialize, webhook_payload, auth_token, output_sanitizer, duration_parse)
- encrypt: additional hardening from long-running agent

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…argo(+43), plan(+32)

- engine: +30 tests — state transitions, retry/backoff strategies, error classification, receipts, resume-from logic, multi-package scenarios
- engine-parallel: +19 tests — concurrent state, dependency ordering, webhook delivery, progress tracking, resume skipping
- registry: +48 tests — HTTP errors (4xx/5xx), rate limiting (429), malformed responses, large payloads, concurrent checks, sparse index edge cases
- cargo: +43 tests — token redaction positions, unicode output, long lines, timeout behavior, env resolution, dry-run variants, idempotent redaction
- plan: +32 tests — diamond dependencies, deep chains, cycle detection, plan determinism, plan ID stability, package filtering, invalid manifests

Total: 3,953 tests (up from 3,764)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- state: +36 tests — corruption handling, atomic writes, receipt states, schema versions, large files, resume partial states, concurrent writes
- config: +71 tests — TOML edge cases, CLI override precedence, retry presets, boolean OR semantics, readiness/webhook/registry merging, validation boundaries
- auth: +35 tests — env var priority, credentials.toml parsing, malformed TOML, unicode/base64 tokens, OIDC detection, cargo_home resolution
- git: +20 tests — dirty tree variants (staged/modified/deleted), branch detection, empty repo, unicode filenames, merge conflicts, shallow clones, submodules
- CLI E2E: +30 tests — help snapshots, doctor output, status with mock registry, plan filtering, error messages, config init variants, quiet mode

Total: 4,152 tests (up from 3,953)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- output-sanitizer: normalize \r\n and bare \r to \n at start of redact_sensitive() for true idempotence
  (fuzz target redact_output found bare \r causing different results across successive calls)
- cargo: merge redundant publish_timeout_message_includes_duration_text test into
  publish_with_very_short_timeout_times_out (eliminates flaky CI failure on Linux llvm-cov)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- failure-modes.md: fix [retry.strategy] nested TOML to flat fields on [retry]
- failure-modes.md: add 6 missing permanent patterns (readme, repository,
  compilation failed, failed to verify, publish is disabled, yanked)
- configuration.md: add missing retry fields (policy, strategy, jitter)
  to [retry] section and example config
- README.md: add missing --retry-strategy, --retry-jitter, --verbose,
  -q/--quiet flags to Options section
- templates: update Rust versions from 1.75 to latest (MSRV is 1.92)
- templates: add --locked to cargo install in azure/circleci templates

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add comprehensive E2E snapshot tests covering previously uncovered
CLI error paths and behaviors:

- publish: missing manifest, invalid manifest content, exit codes
- resume: no state file, corrupted state, empty state, missing manifest
- preflight: non-git directory, --allow-dirty with --skip-ownership-check
- config validate: wrong value type, malformed TOML, invalid nested section
- plan: all publish=false workspace, --format json flag, --verbose single crate
- help text: preflight --help, ci --help
- errors: unknown subcommand, exit code assertions
- inspect-events: with populated events file

Also adds normalize_timing() helper to stabilize snapshots containing
variable duration values.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ning

- output-sanitizer: normalize \r\n and bare \r in tail_lines() (fixes proptest CI failure
  with bare \r in input, same root cause as redact_sensitive fix)
- BDD: +7 edge-case scenarios (empty workspace, missing manifest, corrupted state, etc.)
- integration: +14 pipeline integration tests (cross-module state/events/receipts/locks)
- fuzz: +2 new targets (plan_ordering, event_log_roundtrip), register 5 unregistered targets
- fuzz: harden all existing fuzz targets with better input handling and crash resistance

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…hots), BDD hardening

- engine: +22 tests — retry logic edge cases, state persistence verification, error
  classification matrix, init state snapshots, mixed transition snapshots, proptests
- registry: +34 tests — HTTP error handling (429/500/502/503/504), response parsing,
  version comparison, owner verification, readiness evidence, snapshots, proptests
- state: +22 tests — atomic write safety, receipt completeness with evidence/git context,
  empty packages state, migration/versioning, concurrent access, snapshots, proptests
- plan: +5 snapshot tests — dev-dep cycles, double diamond, mixed dep kinds, prerelease,
  double diamond selected mid
- BDD: hardened workflow scenarios with improved step definitions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…E2E(+16), BDD(+5)

- cargo: +25 tests — tail_lines edge cases (empty, Unicode, mixed endings), env var handling,
  package name validation, prerelease version parsing, 3 snapshots, proptests
- encrypt: +20 snapshot tests — passphrase masking, config display, empty plaintext,
  no-passphrase error, key boundary edge cases, round-trip verification
- facade: +20 pipeline integration tests — full pipeline execution, error propagation,
  resume after partial publish, event log integrity, receipt completeness, lock lifecycle
- CLI E2E: +16 new snapshot tests — clean commands, config validate sections, plan formatting,
  resume edge cases, status multi-package, help subcommands, error messages
- BDD: +5 workflow scenarios — enhanced edge case coverage for publish/resume flows

Total: 4,431 #[test] annotations, 33 fuzz targets

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…sume fixes, env isolation

- Add 36 new insta snapshots across auth(6), cargo-failure(6), config(7),
  plan(5), state(6), engine(2), registry(2), CLI e2e(2)
- Fix mock server hangs: replace blocking recv() and 60s recv_timeout()
  with 5s recv_timeout() across all test files (engine, engine_parallel,
  bdd_workflow, bdd_resume, bdd_doctor, bdd_preflight, bdd_publish,
  bdd_micro_backends, bdd_status, cli_e2e, e2e_doctor, e2e_expanded,
  e2e_preflight, e2e_resume, e2e_status)
- Fix bdd_resume response sequences: correct request counts for
  resume_from_specific_package and force_resume_then_bypasses_mismatch
- Add #[serial] to shipper-environment tests that read CI env vars
- Add normalize_error helper in shipper-state for deterministic snapshots
- Fix shipper-events doc comment indentation
- Add *.log to .gitignore

@coderabbitai coderabbitai 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.

Actionable comments posted: 46

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
ROADMAP.md (2)

7-7: ⚠️ Potential issue | 🟡 Minor

Update Table of Contents to reflect current version.

The ToC still references v0.2.0, but the document has been updated to v0.3.0-rc.1.

📝 Proposed fix
-1. [Current Release: v0.2.0](`#current-release-v020`)
+1. [Current Release: v0.3.0-rc.1](`#current-release-v030-rc1`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ROADMAP.md` at line 7, Update the Table of Contents entry that currently
reads "Current Release: v0.2.0" (and its anchor "(`#current-release-v020`)") to
match the document's new release string "v0.3.0-rc.1" and update the anchor to
the corresponding heading anchor (e.g., "(`#current-release-v030-rc1`)" or
whatever the generated slug for the "Current Release: v0.3.0-rc.1" heading is)
so the ToC label and link both reflect v0.3.0-rc.1.

560-563: ⚠️ Potential issue | 🟡 Minor

Update version history table to reflect current release.

The version history table still marks v0.2.0 as "Current", but v0.3.0-rc.1 is now the current release.

📝 Proposed fix
 | Version | Status | Theme | Notes |
 |---------|--------|-------|-------|
+| v0.3.0-rc.1 | **Current** | User Experience | Multi-registry publishing, sparse index caching, selective resumability |
-| v0.2.0 | **Current** | Evidence & Verification | Parallel publishing, configuration files, readiness verification |
+| v0.2.0 | Released | Evidence & Verification | Parallel publishing, configuration files, readiness verification |
 | v0.1.0 | Released | Core Functionality | Initial release with basic publish workflow |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ROADMAP.md` around lines 560 - 563, The version table currently marks v0.2.0
as "Current"; change the table so the "Version" cell shows v0.3.0-rc.1 with
"Status" set to **Current** (replace the v0.2.0 row), and update the v0.2.0 row
to "Released" under Status with its existing Theme/Notes; ensure the table
includes v0.3.0-rc.1 Theme/Notes (e.g., release candidate details) and that rows
remain properly aligned (refer to the Version entries v0.3.0-rc.1, v0.2.0 as
unique identifiers).
.github/workflows/fuzz.yml (1)

82-100: ⚠️ Potential issue | 🟠 Major

Don't swallow non-crasher fuzz failures.

continue-on-error: true masks step failures, and the workflow only checks for crash artifacts—not whether the fuzz step itself failed. Build errors, missing targets, or toolchain failures produce no artifacts/ directory and leave the job green, creating false-positive test results.

Add an outcome check to catch fuzz failures that don't produce crash artifacts:

Proposed fix
      - name: Run fuzz target
+       id: fuzz_run
        run: |
          cargo fuzz run ${{ matrix.target }} \
            --corpus fuzz/corpus/${{ matrix.target }} \
            -- \
            -max_total_time=${{ steps.duration.outputs.value }} \
            -artifact_prefix=artifacts/${{ matrix.target }}_
        continue-on-error: true

       - name: Check for crashers
         id: check_crashers
         run: |
           if [ -d "artifacts" ] && [ "$(ls -A artifacts 2>/dev/null)" ]; then
             echo "crashers_found=true" >> $GITHUB_OUTPUT
             echo "::error::Fuzz target ${{ matrix.target }} found crashers"
             ls -la artifacts/
           else
             echo "crashers_found=false" >> $GITHUB_OUTPUT
           fi
+
+      - name: Fail on non-crasher fuzz errors
+        if: steps.fuzz_run.outcome == 'failure' && steps.check_crashers.outputs.crashers_found != 'true'
+        run: |
+          echo "::error::cargo fuzz failed before producing crash artifacts"
+          exit 1
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/fuzz.yml around lines 82 - 100, The fuzz run step
currently uses continue-on-error: true and has no id, so non-crasher failures
are hidden; give the "Run fuzz target" step a stable id (e.g., id:
run_fuzz_target), keep continue-on-error: true if you want to collect artifacts,
then in the "Check for crashers" step inspect both the artifacts directory and
the run step outcome (check steps.run_fuzz_target.outcome != 'success') and emit
crashers_found=true plus an error if either crash artifacts exist or the run
step failed (and set crashers_found=false only when no artifacts exist and the
run step outcome is success) so build/toolchain failures are surfaced.
crates/shipper-cargo-failure/src/lib.rs (1)

26-47: ⚠️ Potential issue | 🟡 Minor

Potential false positive with numeric pattern "500".

The pattern "500" on line 40 can match port numbers (e.g., 15003) or version strings. The test on lines 599-605 documents this behavior, but it could cause legitimate non-error messages to be classified as retryable failures.

Consider using a more specific pattern like " 500 " or "500 " to reduce false positives, or document this as an accepted trade-off.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/shipper-cargo-failure/src/lib.rs` around lines 26 - 47,
RETRYABLE_PATTERNS currently contains the bare substring "500" which can
false-match numeric contexts (e.g., ports or versions); update the
RETRYABLE_PATTERNS constant to use a more specific match for HTTP 500 (for
example change "500" to " 500 " or "500 " or use a regex word-boundary variant
like r"\b500\b") so only standalone status codes are matched; modify the entry
for "500" (and similarly "502","503","504" if desired) inside the
RETRYABLE_PATTERNS array or switch to a regex-based check where the matching
code consults the new pattern to avoid the documented false positives.
♻️ Duplicate comments (2)
crates/shipper-cargo/CLAUDE.md (1)

1-29: ⚠️ Potential issue | 🟡 Minor

Same template and formatting issues as other CLAUDE.md files.

This file has identical issues to the other CLAUDE.md files in this PR. Please substitute shipper-cargo for the placeholders and apply the formatting fixes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/shipper-cargo/CLAUDE.md` around lines 1 - 29, The CLAUDE.md uses
placeholders and a broken code fence; replace every placeholder ($name,
$entryPoint, $rootDocs) with the actual crate name "shipper-cargo" (and the
correct primary entry if known), fix the malformed code block (remove the stray
control char and use a proper triple-backtick fenced block with "bash"), clean
up Windows path escaping (use consistent path formatting or a relative path),
and apply the same formatting fixes you applied to other CLAUDE.md files so
headers, bullets, and the "Useful commands" block are stable and readable;
locate and edit the CLAUDE.md in crates/shipper-cargo and update the
placeholders and code fence accordingly.
crates/shipper-cargo-failure/CLAUDE.md (1)

1-29: ⚠️ Potential issue | 🟡 Minor

Same template and formatting issues as other CLAUDE.md files.

This file has identical issues to the other CLAUDE.md files in this PR. Please substitute shipper-cargo-failure for the placeholders and apply the formatting fixes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/shipper-cargo-failure/CLAUDE.md` around lines 1 - 29, Replace template
placeholders and fix formatting in CLAUDE.md: substitute $name with
"shipper-cargo-failure", set $entryPoint to the crate's primary entry (e.g.,
lib.rs or main.rs as applicable), and update $rootDocs to the actual relative
docs path; correct the malformed shell code fence (replace the stray control
character and use a proper ```bash block) and ensure the cargo commands are on
separate lines; normalize backslashes in the workspace path or use forward
slashes for consistency and fix any punctuation/typo issues so the file matches
the other CLAUDE.md files' format.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Cargo.toml`:
- Around line 29-32: The workspace members list in Cargo.toml has inconsistent
indentation for the entries "crates/shipper-output-sanitizer",
"crates/shipper-levels", "crates/shipper-execution-core", and
"crates/shipper-config-runtime" (they use 4 spaces while others use 2); update
those four lines to use the same 2-space indentation as the rest of the members
list so the workspace members block is consistently formatted.

In `@crates/shipper-auth/CLAUDE.md`:
- Around line 1-29: Replace unsubstituted placeholders ($name, $entryPoint,
$rootDocs) with the crate-specific values for shipper-auth (use "shipper-auth"
for crate name and the crate's actual primary entry point), fix the fenced code
block marker from "`ash" to a proper triple-backtick block for bash, convert
Windows path separators in the workspace/root path to POSIX-style or neutral
notation, and ensure the file ends with a trailing newline; update the header
and context text accordingly so the CLAUDE.md content matches the other
corrected crate CLAUDE.md files.

In `@crates/shipper-auth/README.md`:
- Around line 1-36: The README contains template artifacts: remove the
duplicated "# shipper-auth" header, replace the "$entryPoint" placeholder with
the actual source entry (e.g., "src/lib.rs" or the crate's real entry), fix the
malformed code fence by replacing "`�ash" and the trailing single backtick with
a proper triple backtick fence "```bash" ... "```", and correct the broken tests
path "	ests/" to "`tests/`" in the Contributing section; update those exact
tokens ("# shipper-auth", "$entryPoint", "`�ash", "`", and "	ests/") in the
README to render clean crate docs.

In `@crates/shipper-auth/src/lib.rs`:
- Around line 1310-1337: The tests currently treat whitespace-only strings as
valid tokens; instead, update the token handling so whitespace-only credentials
are considered absent: in resolve_token(...) and
token_from_credentials_file(...), trim the extracted token (e.g., token.trim())
and only mark auth as detected / return Some(token) when the trimmed string is
non-empty; if trimmed is empty, return None / detected = false. Ensure
callers/use of token preserve the original stored token only when trimmed
non-empty.
- Around line 258-266: The mask_token function currently slices &str by byte
offsets which can panic on Unicode; update mask_token to operate on Unicode
scalar values via .chars(): compute char_count = token.chars().count(), if
char_count <= 8 return "*" repeated char_count, otherwise build the masked
string by collecting the first four chars
(token.chars().take(4).collect::<String>()) and the last four chars
(token.chars().rev().take(4).collect::<String>() then reverse that collected
tail) and concatenate with "****"; apply the same char-safe approach to the
other occurrence noted (the block around symbols handling at the second
location).
- Around line 269-276: The current is_trusted_publishing_available function
treats empty OIDC env vars as present; change the check to ensure both
ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN are non-empty
strings (e.g., use env::var_os(...).and_then(|s| if s.is_empty() { None } else {
Some(s) }).is_some() or use env::var(...) and verify non-empty), so the function
only returns true when both env vars exist and are not empty; apply the same
non-empty validation to the other identical check found later in the file (the
duplicate OIDC availability check around lines ~1531-1543).
- Around line 410-1761: Tests that mutate global environment or filesystem
(e.g., resolve_token_env_default_takes_priority_over_credentials,
resolve_token_env_registry_takes_priority_over_credentials,
has_token_returns_true_when_found, cargo_home_path_falls_back_to_env_var,
credential/file tests, trusted_publishing_* and many others) must be run
serially to avoid flakiness; add the #[serial] attribute from the serial_test
crate to each affected #[test] function (or to enclosing test modules like
snapshots, proptests, edge_snapshots, error_message_snapshots) and add use
serial_test::serial at the top of the test module so tests that set CARGO_HOME,
CARGO_REGISTRY_TOKEN, CARGO_REGISTRIES_*, or ACTIONS_ID_TOKEN_REQUEST_* run one
at a time.

In `@crates/shipper-cargo-failure/README.md`:
- Around line 1-36: The README contains leftover scaffold artifacts: a duplicate
H1, an unresolved placeholder $entryPoint, a malformed bash code fence, and a
broken tests path; fix by removing the redundant H1, replace $entryPoint with
the actual main entry (or remove the "Source entry point" section) and correct
the fenced code block marker to ```bash (or ```), and update the tests path to
`tests/` in the Contributing section (refer to the README headings and the
"Source entry point", the malformed code fence block containing cargo commands,
and the Contributing line that currently shows "(	ests/, src/)").

In `@crates/shipper-cargo/Cargo.toml`:
- Around line 19-26: The crate declares shipper-output-sanitizer in both
[dependencies] and [dev-dependencies]; remove the duplicate entry from the
[dev-dependencies] section so shipper-output-sanitizer is only listed once as a
regular dependency, leaving the existing shipper-output-sanitizer = { path =
"../shipper-output-sanitizer" } in [dependencies] and deleting the same line
under [dev-dependencies]; keep other dev-dependencies (tempfile,
proptest.workspace, insta, temp-env) unchanged and run cargo check to verify.

In `@crates/shipper-cargo/README.md`:
- Around line 1-36: Remove the duplicate top-level heading and clean up
placeholders in README.md: keep a single H1/title, replace or remove the
unresolved $entryPoint placeholder in the "Source entry point" section (use the
actual entry or omit the section), fix the malformed code fence label that
currently reads "�ash" to a proper "```bash" fenced block for the development
commands, and correct the broken tests path by changing "(	ests/, src/)" to
backticked "`tests/`, `src/`" in the Contributing paragraph so the markdown
renders correctly.

In `@crates/shipper-cargo/src/lib.rs`:
- Around line 1198-1222: These three tests (cargo_program_defaults_to_cargo,
cargo_program_respects_shipper_cargo_bin, cargo_program_empty_env_returns_empty)
mutate the process-wide SHIPPER_CARGO_BIN env var and must be serialized; add
the #[serial] attribute from the serial_test crate above each test (or import
serial and annotate all three) so they run sequentially and cannot race with
other tests that call cargo_program().
- Around line 1217-1221: The test cargo_program_empty_env_returns_empty
currently treats SHIPPER_CARGO_BIN="" as a valid program path, which leads to
Command::new("") later; change cargo_program() to treat an environment value
that is empty or only whitespace as unset (e.g., if
env::var("SHIPPER_CARGO_BIN").ok().filter(|s| !s.trim().is_empty()).map(...) {
... } else fallback to "cargo"), and update the test
cargo_program_empty_env_returns_empty (and any related tests) to expect the
fallback ("cargo") when SHIPPER_CARGO_BIN is set to "" rather than returning an
empty string. Ensure references to cargo_program and the test name are used when
locating the change.

In `@crates/shipper-cargo/tests/redaction_contract.rs`:
- Around line 4-9: The test
cargo_crate_redact_sensitive_matches_output_sanitizer is tautological because it
only compares two implementations; change it to call
shipper_cargo::redact_sensitive(input) and assert the returned string does not
contain the raw secrets ("shipper_secret_token" and "abc123") and/or matches the
expected redaction pattern; in other words, replace the equality check against
shipper_output_sanitizer::redact_sensitive with direct assertions that
redact_sensitive(input) does not contain the original tokens (and optionally
that it contains the expected mask placeholder) so the test fails if secrets are
leaked.

In `@crates/shipper-chunking/CLAUDE.md`:
- Around line 1-29: The CLAUDE.md contains unsubstituted template variables and
syntax/platform issues: replace $name with "shipper-chunking", $entryPoint with
"src/lib.rs" (or the crate's actual entry file), and $rootDocs with the relative
path "../../CLAUDE.md"; fix the code fence header from "`ash" to "```bash"; and
convert Windows backslashes in the workspace root and link
("h:\Code\Rust\shipper" and "$rootDocs\CLAUDE.md") to cross-platform
forward-slash/relative forms (e.g., "h:/Code/Rust/shipper" or preferably remove
the absolute path and use "../../CLAUDE.md" for the link). Ensure the commands
remain inside a proper fenced block and update any remaining placeholders
consistently.

In `@crates/shipper-chunking/README.md`:
- Around line 1-36: Remove duplicate headings and repeated description in README
(the second "# shipper-chunking" and the repeated "Chunking helpers..."
sentence), replace the unsubstituted placeholder "$entryPoint" with a meaningful
entry point description or remove the "Source entry point" section if none
exists, fix the fenced code block marker by changing the malformed "`ash" to a
proper triple-backtick with language "bash", correct the typo "(	ests/" to
"(tests/" in the Contributing line, and ensure the file ends with a trailing
newline; update the README content in crates/shipper-chunking/README.md
accordingly.

In `@crates/shipper-chunking/tests/chunking_bdd.rs`:
- Around line 1-46: Add a property-based test using proptest that generates
arbitrary vectors of items and an integer max_concurrent >= 1 and asserts the
chunking invariants for chunk_by_max_concurrent: for any generated items and
limit, the flattened sequence of chunks equals the original items (order
preserved, no loss/duplication), every chunk has length <= max_concurrent, and
the total number of chunks is consistent (e.g., sum of chunk lengths ==
items.len()); import proptest, create a test (e.g.,
prop_bdd_chunking_invariants) that uses proptest::collection::vec(...) for items
and a strategy for max_concurrent with a min of 1, call
chunk_by_max_concurrent(&items, max_concurrent) and check the three invariants
above to catch regressions.

In `@crates/shipper-cli/CLAUDE.md`:
- Line 29: The file CLAUDE.md is missing a trailing newline; open the CLAUDE.md
file and add a single newline character at the end of the file (ensure the file
ends with exactly one '\n'), then save so markdownlint MD047 is satisfied.
- Around line 14-20: Replace the malformed code fence marker "ash" with a proper
fenced code block using triple backticks and the "bash" language tag and ensure
the block is closed with triple backticks; in CLAUDE.md locate the code block
starting with "`�ash" and change it to "```bash" and add a closing "```" after
the cargo commands so the snippet renders with correct syntax highlighting.
- Around line 3-10: Replace the template placeholders in CLAUDE.md with concrete
values: change all occurrences of $name to "shipper-cli", replace $entryPoint
with "src/main.rs", and remove or replace $rootDocs (e.g., remove the $rootDocs
variable or use a relative path) so the file no longer contains unsubstituted
template variables; ensure the header and Scope section reflect the actual crate
name and entry point.

In `@crates/shipper-cli/src/main.rs`:
- Around line 1457-1475: The cleanup currently deletes directories as it
iterates dirs_to_clean which can leave partial state if a later subdirectory has
an active lock; before calling clean_single_dir, add a read-only lock-validation
pass that scans the same dirs_to_clean (derived from abs_state and registry
subdirs) and checks for active locks (unless force is true), returning an error
and aborting before any mutation if any lock is held; only if the validation
passes (or force is set) proceed to the existing deletion loop that calls
clean_single_dir(workspace_root, keep_receipt, force). Apply the same two-phase
validation-then-delete change for the other block noted around lines 1489-1516.
- Around line 450-486: The loop over target_registries currently prints
per-registry banners/progress and calls print_receipt per registry, which breaks
JSON output when cli.format == "json"; instead collect each registry's receipt
(from engine::run_publish) into a Vec and, after the loop, if cli.format is json
serialize and print a single JSON array/object (suppress banners/progress during
iteration), otherwise keep the existing human-mode behavior (show
banner/progress and call print_receipt per registry). Apply the same change to
the analogous publish/resume loop (the other target_registries loop that also
calls ProgressReporter, engine::run_publish and print_receipt).
- Around line 467-479: The ProgressReporter is being preset to package 1 but
never updated, so multi-package runs stall; either remove the pre-call to
ProgressReporter::set_package(...) and let the engine drive progress via the
existing reporter, or explicitly wire ProgressReporter into the publish/resume
flow so engine::run_publish and engine::run_resume (or the reporter callbacks
they use) call ProgressReporter::set_package/update as packages change; locate
the ProgressReporter instantiation and the set_package call near
current_planned/current_opts/reporter and either delete that initial
set_package(1, ...) or pass &mut progress (or register progress callbacks) into
engine::run_publish/run_resume so the progress bar advances for each package
before calling progress.finish().
- Around line 549-565: The loop uses the original opts when calling run_doctor
so checks run against the shared state_dir instead of the registry-scoped
directory; create a registry-scoped RuntimeOptions (clone opts into a new
variable, update its state_dir to re-root under state_dir/<registry> or the same
logic used by publish/resume) and pass that into run_doctor (use the same scope
where current_planned.plan.registry is set). Ensure you reference and update the
RuntimeOptions instance used for runtime checks (opts -> registry_opts) before
calling run_doctor(&current_planned, &registry_opts, &mut reporter).

In `@crates/shipper-cli/tests/bdd_doctor.rs`:
- Around line 87-264: The tests mutate process-wide env/state and must be run
serially: add a use serial_test::serial; import at top of the test file and
annotate each affected test function with #[serial] (e.g. above
given_clean_environment_when_running_doctor_then_reports_environment_info,
given_cargo_is_installed_when_running_doctor_then_cargo_version_is_detected,
given_no_cargo_registry_token_when_running_doctor_then_warns_about_missing_token,
given_valid_workspace_when_running_doctor_then_checks_workspace_health, and
given_nonexistent_state_dir_when_running_doctor_then_reports_will_be_created) so
they run sequentially and avoid cross-test interference.

In `@crates/shipper-cli/tests/bdd_error_handling.rs`:
- Around line 723-738: Replace the permissive existence checks that let
scenarios pass when files are missing with hard assertions so missing
persistence fails the test: instead of wrapping the state.json handling in if
state_path.exists(), assert that state_path.exists() (with a clear message like
"state.json not written"), then read and parse the file into
state/serde_json::Value and continue the same checks (e.g. the packages lookup
and core package assertion in this block); do the same for the receipt.json
checks referenced (the blocks around the other ranges) so they assert presence
before reading rather than silently skipping when the file is absent.
- Around line 219-239: The test helper spawn_registry uses
server.recv_timeout(Duration::from_secs(30)) which can add long stalls when
tests overprovision expected_requests; change the hardcoded 30s to a much
shorter, shared constant (e.g., TEST_SERVER_RECV_TIMEOUT =
Duration::from_secs(1) or Duration::from_millis(500)) and replace all
occurrences of server.recv_timeout(Duration::from_secs(30)) in spawn_registry
and the other test helpers to use that constant so the loop breaks quickly when
fewer requests arrive; keep the existing Ok(Some(r)) / break behavior unchanged.

In `@crates/shipper-cli/tests/bdd_error_recovery.rs`:
- Around line 325-343: The test currently invokes the real cargo and default
registry, causing non-hermetic, network-dependent behavior; instead create a
local tiny_http mock registry and a fake cargo executable that simulates cargo
behavior (responds to metadata, skip/publish flow and then deterministically
fails on publish), start the tiny_http server for registry/index endpoints,
point the test to the mock registry (via environment or a temporary
.cargo/config) and ensure shipper_cmd() is run with the PATH or CARGO env
updated to pick up the fake cargo; update the invocation around shipper_cmd() /
td.path().join("Cargo.toml") / --state-dir & state_dir so the resume run hits
the mock registry and fake cargo and then assert the stderr contains the skip
message and the deterministic terminal failure.

In `@crates/shipper-cli/tests/bdd_micro_backends.rs`:
- Around line 131-234: The two tests that mutate process-wide environment
(given_dependency_chain_and_no_token_when_preflight_with_micro_backends_then_stable_output
and
given_dependency_chain_and_no_token_when_preflight_with_all_micro_crates_then_stable_output)
must be run serially to avoid cross-test pollution: add the #[serial] attribute
from the serial_test crate above each test and ensure serial_test::serial is
imported in the test module so tests that call env("CARGO_HOME", ...) and
env_remove(...) execute one at a time.

In `@crates/shipper-cli/tests/bdd_parallel.rs`:
- Around line 435-669: Tests that mutate PATH/REAL_CARGO/SHIPPER_* run in
parallel and interfere; mark the tests that set those env vars with #[serial] to
run them serially. Add use serial_test::serial; (near other imports) and
annotate the test functions
given_partially_published_workspace_when_resuming_then_skips_completed_levels,
given_failure_in_one_level_when_continuing_then_stops_subsequent_levels, and
given_max_concurrent_setting_when_publishing_then_limits_parallelism with
#[serial] directly above their fn declarations so each test that rewrites
PATH/SHIPPER_* runs in isolation.
- Around line 231-260: In prepend_fake_cargo and prepend_fake_cargo_failing,
resolve the real cargo path to an absolute path before you mutate PATH (e.g.,
call which/resolution on std::env::var("CARGO") or use
std::env::current_exe/which equivalent) so that real_cargo is an absolute path
(not the bare "cargo") before creating fake_bin and prepending it; keep
referencing real_cargo, fake_cargo_bin_path and
create_fake_cargo_proxy/create_fake_cargo_failing to locate the logic to change.
Also ensure tests that mutate PATH, REAL_CARGO and SHIPPER_CARGO_BIN are
annotated with #[serial] from serial_test to prevent parallel runs.

In `@crates/shipper-cli/tests/bdd_preflight.rs`:
- Around line 261-270: The test currently only asserts aggregate counts in
stdout, which allows package classifications to be swapped; update the
assertions to assert the package/state pairing instead by checking that stdout
contains the specific package lines for core@0.1.0 as already published and
app@0.1.0 as new (e.g., assert stdout contains the "Already published:
core@0.1.0" and "New crates: app@0.1.0" strings) using the existing stdout
variable so the test verifies per-package status rather than just totals.
- Around line 343-353: The tests invoking
shipper_cmd(...).arg("--manifest-path")...preflight must mock the crates
registry to avoid real network calls; call spawn_registry(vec![404, 404], 0)
before the shipper_cmd invocation, pass its base URL to the command via
.arg("--api-base").arg(&registry.base_url), and after the assertion call
registry.join() to clean up; do the same change for the other block referenced
(lines 374-385) so both expected-zero-request tests use the local mock.

In `@crates/shipper-cli/tests/bdd_publish_advanced.rs`:
- Around line 7-9: Add the #[serial] attribute from the serial_test crate to
every test function in this file (i.e., replace or augment each existing #[test]
on the test functions) so tests run serially instead of relying on the comment
workaround; also import the attribute (use serial_test::serial) near the top if
not already present so the attribute resolves. Ensure you add #[serial] to all
test functions in bdd_publish_advanced.rs to match other tests like
bdd_resume.rs and bdd_workflow.rs and prevent concurrent tiny_http server
resource exhaustion on Windows.

In `@crates/shipper-cli/tests/bdd_publish_flow.rs`:
- Around line 196-227: The test
given_unpublished_workspace_when_plan_then_shows_publish_order_without_side_effects
currently only checks presence of "core@0.1.0", "utils@0.1.0", and "app@0.1.0";
add an assertion that enforces their order by locating their positions in the
captured stdout (e.g., use stdout.find("core@0.1.0"),
stdout.find("utils@0.1.0"), stdout.find("app@0.1.0")) and assert pos_core <
pos_utils < pos_app so the plan output respects dependency order while keeping
the existing non-destructive .shipper check.

In `@crates/shipper-cli/tests/bdd_workflow.rs`:
- Around line 1567-1575: Add an assertion to the preflight (dry-run) test to
verify events.jsonl is not created: in the same test block that currently checks
state_dir.join("state.json") and state_dir.join("receipt.json"), also assert
that state_dir.join("events.jsonl").exists() is false with a clear failure
message (e.g., "preflight (dry-run) should not create events.jsonl") so the test
fails if any event log is emitted; this change should be made in the test
function in crates/shipper-cli/tests/bdd_workflow.rs where state_dir is used.
- Around line 22-162: This file duplicates shared BDD helpers—extract the common
helpers into a single test-support module and import it here: move functions and
types like write_file, shipper_cmd, TestRegistry (and its join), spawn_registry,
spawn_doctor_registry, path_sep, create_fake_cargo_proxy, fake_cargo_bin_path,
setup_fake_cargo, and fast_args into the new module, update this test to use
that module's public API (remove local definitions), and update other BDD test
files (the same duplicated regions) to import the same test-support module so
all tests share one implementation.
- Around line 1996-2035: In bdd_ci_github_actions_produces_valid_yaml_steps
replace the multiple substring asserts with a YAML validity check and an insta
snapshot assertion: parse stdout as YAML to ensure it's valid, then call
insta::assert_snapshot!() on the full stdout to capture the template; update the
test to import/enable insta and any YAML parser you already use (or serde_yaml)
and apply the same refactor to the GitLab and CircleCI tests (functions around
lines noted in the review) so structural regressions are caught by snapshots
instead of simple contains() checks.

In `@crates/shipper-cli/tests/cli_e2e.rs`:
- Around line 290-310: The snapshot in crates/shipper-cli/tests/cli_e2e.rs
contains hardcoded git metadata (git_commit, git_branch, git_dirty) that vary
per environment; update the test to normalize those values before asserting by
replacing the concrete values with stable placeholders (e.g. <GIT_COMMIT>,
<GIT_BRANCH>, <GIT_DIRTY>) or by calling the same normalization helper used
previously (the function used in the other test—apply_snapshot_sanitizer or
similar) so the snapshot assertion in this test no longer depends on
environment-specific git_commit/git_branch/git_dirty values.
- Around line 236-256: The snapshot includes environment-specific git fields
(git_commit, git_branch, git_dirty) that aren't being normalized by
normalize_output, so update the normalize_output function in tests/cli_e2e.rs
(the function currently handling "  Commit: " and "  Branch: ") to also match
and replace lines starting with "git_commit:", "git_branch:", and "git_dirty:"
with stable placeholders (e.g., <GIT_COMMIT>, <GIT_BRANCH>, <GIT_DIRTY>) and
then update the expected snapshot block to use those placeholders instead of
concrete values.

In `@crates/shipper-cli/tests/e2e_doctor.rs`:
- Around line 48-77: The test server can run past a panic and the fixed
expected_requests loop is brittle; modify TestRegistry and spawn_registry to
support graceful shutdown by adding a shared Arc<AtomicBool> (e.g., stop_flag)
stored on TestRegistry, have the spawned thread loop until stop_flag is set
(using recv_timeout in a short interval to remain responsive) instead of relying
on expected_requests, and replace join(self) with a shutdown method that sets
stop_flag and then joins the thread handle; update callers to call that shutdown
method (or ensure Drop triggers it) so the server thread always exits promptly
even if tests panic or request counts change.
- Around line 175-206: The test doctor_reports_missing_token unnecessarily wraps
the command in temp_env::with_vars while the closure only sets variables to None
and the Command already calls env_remove; remove the temp_env::with_vars wrapper
and run the shipper_cmd()... chain directly (keep the same args,
.env("CARGO_HOME", ...), .env_remove("CARGO_REGISTRY_TOKEN") and
.env_remove("CARGO_REGISTRIES_CRATES_IO_TOKEN") calls) so the subprocess
environment is controlled only via the Command; update the test body accordingly
and leave spawn_registry(1) / registry.join() unchanged.
- Around line 146-173: The test doctor_detects_token_when_set currently mutates
the process environment via temp_env::with_vars which can cause races; instead
remove the temp_env::with_vars wrapper and set the token only on the subprocess
by calling .env("CARGO_REGISTRY_TOKEN", "secret-test-token") on the Command
returned by shipper_cmd() (keep the existing .env("CARGO_HOME", ...) and
.env_remove("CARGO_REGISTRIES_CRATES_IO_TOKEN") as needed), or alternatively add
#[serial] to the test if global env mutation is desired; update imports/usages
accordingly so the process-wide temp_env is not used.

In `@RELEASE_CHECKLIST_v0.3.0.md`:
- Around line 21-23: Add a blank line before the fenced code block that starts
with "```bash" so the Markdown renders correctly; locate the checklist item line
"- [ ] Publish to crates.io (dry-run first):" and insert a single empty line
between that paragraph and the following "```bash" fence to separate the block
from the preceding text.
- Around line 22-50: The publish checklist is out of dependency order and
missing crates; reorder and update the bash block so crates are topologically
sorted and include the missing workspace crates (shipper-levels,
shipper-storage, shipper-git, shipper-chunking). Specifically, ensure
dependencies like shipper-encrypt, shipper-webhook, shipper-retry,
shipper-duration, and shipper-levels appear before shipper-types; place
shipper-state before shipper-execution-core; place shipper-chunking before
shipper-engine-parallel; and add shipper-storage and shipper-git in the correct
positions according to the workspace dependency graph before running the cargo
publish dry-run list.

In `@RELEASE_NOTES_v0.3.0.md`:
- Around line 20-22: Add a single blank line immediately before the fenced code
block that begins with "```bash" containing the command "shipper publish
--resume-from my-critical-crate" so the code block is surrounded by blank lines
per Markdown best practices; locate the fenced block in RELEASE_NOTES_v0.3.0.md
and insert one empty line above the "```bash" marker.

---

Outside diff comments:
In @.github/workflows/fuzz.yml:
- Around line 82-100: The fuzz run step currently uses continue-on-error: true
and has no id, so non-crasher failures are hidden; give the "Run fuzz target"
step a stable id (e.g., id: run_fuzz_target), keep continue-on-error: true if
you want to collect artifacts, then in the "Check for crashers" step inspect
both the artifacts directory and the run step outcome (check
steps.run_fuzz_target.outcome != 'success') and emit crashers_found=true plus an
error if either crash artifacts exist or the run step failed (and set
crashers_found=false only when no artifacts exist and the run step outcome is
success) so build/toolchain failures are surfaced.

In `@crates/shipper-cargo-failure/src/lib.rs`:
- Around line 26-47: RETRYABLE_PATTERNS currently contains the bare substring
"500" which can false-match numeric contexts (e.g., ports or versions); update
the RETRYABLE_PATTERNS constant to use a more specific match for HTTP 500 (for
example change "500" to " 500 " or "500 " or use a regex word-boundary variant
like r"\b500\b") so only standalone status codes are matched; modify the entry
for "500" (and similarly "502","503","504" if desired) inside the
RETRYABLE_PATTERNS array or switch to a regex-based check where the matching
code consults the new pattern to avoid the documented false positives.

In `@ROADMAP.md`:
- Line 7: Update the Table of Contents entry that currently reads "Current
Release: v0.2.0" (and its anchor "(`#current-release-v020`)") to match the
document's new release string "v0.3.0-rc.1" and update the anchor to the
corresponding heading anchor (e.g., "(`#current-release-v030-rc1`)" or whatever
the generated slug for the "Current Release: v0.3.0-rc.1" heading is) so the ToC
label and link both reflect v0.3.0-rc.1.
- Around line 560-563: The version table currently marks v0.2.0 as "Current";
change the table so the "Version" cell shows v0.3.0-rc.1 with "Status" set to
**Current** (replace the v0.2.0 row), and update the v0.2.0 row to "Released"
under Status with its existing Theme/Notes; ensure the table includes
v0.3.0-rc.1 Theme/Notes (e.g., release candidate details) and that rows remain
properly aligned (refer to the Version entries v0.3.0-rc.1, v0.2.0 as unique
identifiers).

---

Duplicate comments:
In `@crates/shipper-cargo-failure/CLAUDE.md`:
- Around line 1-29: Replace template placeholders and fix formatting in
CLAUDE.md: substitute $name with "shipper-cargo-failure", set $entryPoint to the
crate's primary entry (e.g., lib.rs or main.rs as applicable), and update
$rootDocs to the actual relative docs path; correct the malformed shell code
fence (replace the stray control character and use a proper ```bash block) and
ensure the cargo commands are on separate lines; normalize backslashes in the
workspace path or use forward slashes for consistency and fix any
punctuation/typo issues so the file matches the other CLAUDE.md files' format.

In `@crates/shipper-cargo/CLAUDE.md`:
- Around line 1-29: The CLAUDE.md uses placeholders and a broken code fence;
replace every placeholder ($name, $entryPoint, $rootDocs) with the actual crate
name "shipper-cargo" (and the correct primary entry if known), fix the malformed
code block (remove the stray control char and use a proper triple-backtick
fenced block with "bash"), clean up Windows path escaping (use consistent path
formatting or a relative path), and apply the same formatting fixes you applied
to other CLAUDE.md files so headers, bullets, and the "Useful commands" block
are stable and readable; locate and edit the CLAUDE.md in crates/shipper-cargo
and update the placeholders and code fence accordingly.

Comment thread Cargo.toml
Comment on lines +29 to +32
"crates/shipper-output-sanitizer",
"crates/shipper-levels",
"crates/shipper-execution-core",
"crates/shipper-config-runtime",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Inconsistent indentation in workspace members list.

Lines 29-32 use 4-space indentation while the rest of the members use 2-space indentation.

📝 Proposed fix
   "crates/shipper-policy",
-    "crates/shipper-output-sanitizer",
-    "crates/shipper-levels",
-    "crates/shipper-execution-core",
-    "crates/shipper-config-runtime",
+  "crates/shipper-output-sanitizer",
+  "crates/shipper-levels",
+  "crates/shipper-execution-core",
+  "crates/shipper-config-runtime",
 ]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"crates/shipper-output-sanitizer",
"crates/shipper-levels",
"crates/shipper-execution-core",
"crates/shipper-config-runtime",
"crates/shipper-output-sanitizer",
"crates/shipper-levels",
"crates/shipper-execution-core",
"crates/shipper-config-runtime",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Cargo.toml` around lines 29 - 32, The workspace members list in Cargo.toml
has inconsistent indentation for the entries "crates/shipper-output-sanitizer",
"crates/shipper-levels", "crates/shipper-execution-core", and
"crates/shipper-config-runtime" (they use 4 spaces while others use 2); update
those four lines to use the same 2-space indentation as the rest of the members
list so the workspace members block is consistently formatted.

Comment thread crates/shipper-auth/CLAUDE.md Outdated
Comment on lines +1 to +29
# CLAUDE.md

This file provides agent-specific guidance for working in crate $name.

## Scope

- Crate: $name
- Path: crates/shipper-auth
- Workspace root: h:\Code\Rust\shipper
- Primary entry: $entryPoint

## Useful commands

`ash
cargo check -p shipper-auth
cargo test -p shipper-auth
cargo test -p shipper-auth --all-features
cargo fmt -p shipper-auth
cargo clippy -p shipper-auth --all-targets --all-features -- -D warnings
`

## Context

- Keep changes small and targeted to the crate’s existing abstractions.
- Preserve public API compatibility unless the request explicitly asks for breaking changes.
- When touching serialization or state formats, update tests and related snapshots in the same crate.
- Prefer using existing fixtures and helpers rather than introducing inline test data.

For full workspace guidance, see [$rootDocs\CLAUDE.md](H:\Code\Rust\shipper\CLAUDE.md). No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Same template and formatting issues as other CLAUDE.md files.

This file has the same issues identified in crates/shipper-chunking/CLAUDE.md:

  • Unsubstituted placeholders ($name, $entryPoint, $rootDocs)
  • Code block syntax (`ash instead of ```bash)
  • Windows path separators
  • Missing trailing newline

Please apply the same pattern of fixes as suggested for the other CLAUDE.md files, substituting shipper-auth for the crate name.

🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 29-29: Files should end with a single newline character

(MD047, single-trailing-newline)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/shipper-auth/CLAUDE.md` around lines 1 - 29, Replace unsubstituted
placeholders ($name, $entryPoint, $rootDocs) with the crate-specific values for
shipper-auth (use "shipper-auth" for crate name and the crate's actual primary
entry point), fix the fenced code block marker from "`ash" to a proper
triple-backtick block for bash, convert Windows path separators in the
workspace/root path to POSIX-style or neutral notation, and ensure the file ends
with a trailing newline; update the header and context text accordingly so the
CLAUDE.md content matches the other corrected crate CLAUDE.md files.

Comment thread crates/shipper-auth/README.md
Comment on lines +258 to 266
/// Mask a token for safe display.
///
/// Shows the first 4 and last 4 characters, replacing the middle with
/// `****`. Tokens of 8 characters or fewer are fully masked.
pub fn mask_token(token: &str) -> String {
if token.len() <= 8 {
return "*".repeat(token.len());
}
format!("{}****{}", &token[..4], &token[token.len() - 4..])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle Unicode tokens without byte slicing.

Line 266 slices &str by byte offsets. Any token longer than 8 bytes whose 4th or (len - 4)th byte is not a UTF-8 boundary will panic, and the new Unicode-token coverage makes that input class reachable.

💡 Proposed fix
 pub fn mask_token(token: &str) -> String {
-    if token.len() <= 8 {
-        return "*".repeat(token.len());
-    }
-    format!("{}****{}", &token[..4], &token[token.len() - 4..])
+    let chars: Vec<char> = token.chars().collect();
+    if chars.len() <= 8 {
+        return "*".repeat(chars.len());
+    }
+    let head: String = chars[..4].iter().collect();
+    let tail: String = chars[chars.len() - 4..].iter().collect();
+    format!("{head}****{tail}")
 }

Also applies to: 1352-1367

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/shipper-auth/src/lib.rs` around lines 258 - 266, The mask_token
function currently slices &str by byte offsets which can panic on Unicode;
update mask_token to operate on Unicode scalar values via .chars(): compute
char_count = token.chars().count(), if char_count <= 8 return "*" repeated
char_count, otherwise build the masked string by collecting the first four chars
(token.chars().take(4).collect::<String>()) and the last four chars
(token.chars().rev().take(4).collect::<String>() then reverse that collected
tail) and concatenate with "****"; apply the same char-safe approach to the
other occurrence noted (the block around symbols handling at the second
location).

Comment on lines +269 to +276
/// Detect whether trusted publishing (GitHub Actions OIDC) is available.
///
/// Returns `true` when both `ACTIONS_ID_TOKEN_REQUEST_URL` and
/// `ACTIONS_ID_TOKEN_REQUEST_TOKEN` environment variables are set,
/// indicating a GitHub Actions environment with OIDC token support.
pub fn is_trusted_publishing_available() -> bool {
env::var_os("ACTIONS_ID_TOKEN_REQUEST_URL").is_some()
&& env::var_os("ACTIONS_ID_TOKEN_REQUEST_TOKEN").is_some()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reject empty OIDC env values.

Lines 275-276 currently return true when both GitHub Actions OIDC variables are present but empty. That produces a false positive for trusted publishing availability even though the token request cannot succeed.

Also applies to: 1531-1543

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/shipper-auth/src/lib.rs` around lines 269 - 276, The current
is_trusted_publishing_available function treats empty OIDC env vars as present;
change the check to ensure both ACTIONS_ID_TOKEN_REQUEST_URL and
ACTIONS_ID_TOKEN_REQUEST_TOKEN are non-empty strings (e.g., use
env::var_os(...).and_then(|s| if s.is_empty() { None } else { Some(s)
}).is_some() or use env::var(...) and verify non-empty), so the function only
returns true when both env vars exist and are not empty; apply the same
non-empty validation to the other identical check found later in the file (the
duplicate OIDC availability check around lines ~1531-1543).

Comment on lines +146 to +173
/// 3. Doctor detects CARGO_REGISTRY_TOKEN when set
#[test]
fn doctor_detects_token_when_set() {
let td = tempdir().expect("tempdir");
create_workspace(td.path());
fs::create_dir_all(td.path().join("cargo-home")).expect("mkdir");

let registry = spawn_registry(1);

temp_env::with_vars(
[("CARGO_REGISTRY_TOKEN", Some("secret-test-token"))],
|| {
shipper_cmd()
.arg("--manifest-path")
.arg(td.path().join("Cargo.toml"))
.arg("--api-base")
.arg(&registry.base_url)
.arg("doctor")
.env("CARGO_HOME", td.path().join("cargo-home"))
.env_remove("CARGO_REGISTRIES_CRATES_IO_TOKEN")
.assert()
.success()
.stdout(contains("auth_type: token (detected)"));
},
);

registry.join();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing #[serial] attribute, or simplify by using Command::env() instead of temp_env.

This test uses temp_env::with_vars which modifies the current process's environment, creating potential race conditions with parallel tests. Per coding guidelines: "Use #[serial] from serial_test to isolate tests that mutate environment variables."

However, since only the subprocess needs CARGO_REGISTRY_TOKEN, you can avoid global mutation entirely by using Command's .env() method:

Proposed fix using Command::env()
 #[test]
 fn doctor_detects_token_when_set() {
     let td = tempdir().expect("tempdir");
     create_workspace(td.path());
     fs::create_dir_all(td.path().join("cargo-home")).expect("mkdir");

     let registry = spawn_registry(1);

-    temp_env::with_vars(
-        [("CARGO_REGISTRY_TOKEN", Some("secret-test-token"))],
-        || {
-            shipper_cmd()
-                .arg("--manifest-path")
-                .arg(td.path().join("Cargo.toml"))
-                .arg("--api-base")
-                .arg(&registry.base_url)
-                .arg("doctor")
-                .env("CARGO_HOME", td.path().join("cargo-home"))
-                .env_remove("CARGO_REGISTRIES_CRATES_IO_TOKEN")
-                .assert()
-                .success()
-                .stdout(contains("auth_type: token (detected)"));
-        },
-    );
+    shipper_cmd()
+        .arg("--manifest-path")
+        .arg(td.path().join("Cargo.toml"))
+        .arg("--api-base")
+        .arg(&registry.base_url)
+        .arg("doctor")
+        .env("CARGO_HOME", td.path().join("cargo-home"))
+        .env("CARGO_REGISTRY_TOKEN", "secret-test-token")
+        .env_remove("CARGO_REGISTRIES_CRATES_IO_TOKEN")
+        .assert()
+        .success()
+        .stdout(contains("auth_type: token (detected)"));

     registry.join();
 }

As per coding guidelines: "Use #[serial] from serial_test to isolate tests that mutate environment variables."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// 3. Doctor detects CARGO_REGISTRY_TOKEN when set
#[test]
fn doctor_detects_token_when_set() {
let td = tempdir().expect("tempdir");
create_workspace(td.path());
fs::create_dir_all(td.path().join("cargo-home")).expect("mkdir");
let registry = spawn_registry(1);
temp_env::with_vars(
[("CARGO_REGISTRY_TOKEN", Some("secret-test-token"))],
|| {
shipper_cmd()
.arg("--manifest-path")
.arg(td.path().join("Cargo.toml"))
.arg("--api-base")
.arg(&registry.base_url)
.arg("doctor")
.env("CARGO_HOME", td.path().join("cargo-home"))
.env_remove("CARGO_REGISTRIES_CRATES_IO_TOKEN")
.assert()
.success()
.stdout(contains("auth_type: token (detected)"));
},
);
registry.join();
}
/// 3. Doctor detects CARGO_REGISTRY_TOKEN when set
#[test]
fn doctor_detects_token_when_set() {
let td = tempdir().expect("tempdir");
create_workspace(td.path());
fs::create_dir_all(td.path().join("cargo-home")).expect("mkdir");
let registry = spawn_registry(1);
shipper_cmd()
.arg("--manifest-path")
.arg(td.path().join("Cargo.toml"))
.arg("--api-base")
.arg(&registry.base_url)
.arg("doctor")
.env("CARGO_HOME", td.path().join("cargo-home"))
.env("CARGO_REGISTRY_TOKEN", "secret-test-token")
.env_remove("CARGO_REGISTRIES_CRATES_IO_TOKEN")
.assert()
.success()
.stdout(contains("auth_type: token (detected)"));
registry.join();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/shipper-cli/tests/e2e_doctor.rs` around lines 146 - 173, The test
doctor_detects_token_when_set currently mutates the process environment via
temp_env::with_vars which can cause races; instead remove the
temp_env::with_vars wrapper and set the token only on the subprocess by calling
.env("CARGO_REGISTRY_TOKEN", "secret-test-token") on the Command returned by
shipper_cmd() (keep the existing .env("CARGO_HOME", ...) and
.env_remove("CARGO_REGISTRIES_CRATES_IO_TOKEN") as needed), or alternatively add
#[serial] to the test if global env mutation is desired; update imports/usages
accordingly so the process-wide temp_env is not used.

Comment on lines +175 to +206
/// 4. Doctor reports missing token when not set
#[test]
fn doctor_reports_missing_token() {
let td = tempdir().expect("tempdir");
create_workspace(td.path());
fs::create_dir_all(td.path().join("cargo-home")).expect("mkdir");

let registry = spawn_registry(1);

temp_env::with_vars(
[
("CARGO_REGISTRY_TOKEN", None::<&str>),
("CARGO_REGISTRIES_CRATES_IO_TOKEN", None::<&str>),
],
|| {
shipper_cmd()
.arg("--manifest-path")
.arg(td.path().join("Cargo.toml"))
.arg("--api-base")
.arg(&registry.base_url)
.arg("doctor")
.env("CARGO_HOME", td.path().join("cargo-home"))
.env_remove("CARGO_REGISTRY_TOKEN")
.env_remove("CARGO_REGISTRIES_CRATES_IO_TOKEN")
.assert()
.success()
.stdout(contains("NONE FOUND"));
},
);

registry.join();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Same issue: temp_env::with_vars used unnecessarily.

This test also uses temp_env::with_vars but the closure only sets vars to None and then uses env_remove on the Command anyway. The temp_env wrapper is redundant here since env_remove already handles the subprocess environment.

Proposed fix to remove unnecessary temp_env wrapper
 #[test]
 fn doctor_reports_missing_token() {
     let td = tempdir().expect("tempdir");
     create_workspace(td.path());
     fs::create_dir_all(td.path().join("cargo-home")).expect("mkdir");

     let registry = spawn_registry(1);

-    temp_env::with_vars(
-        [
-            ("CARGO_REGISTRY_TOKEN", None::<&str>),
-            ("CARGO_REGISTRIES_CRATES_IO_TOKEN", None::<&str>),
-        ],
-        || {
-            shipper_cmd()
-                .arg("--manifest-path")
-                .arg(td.path().join("Cargo.toml"))
-                .arg("--api-base")
-                .arg(&registry.base_url)
-                .arg("doctor")
-                .env("CARGO_HOME", td.path().join("cargo-home"))
-                .env_remove("CARGO_REGISTRY_TOKEN")
-                .env_remove("CARGO_REGISTRIES_CRATES_IO_TOKEN")
-                .assert()
-                .success()
-                .stdout(contains("NONE FOUND"));
-        },
-    );
+    shipper_cmd()
+        .arg("--manifest-path")
+        .arg(td.path().join("Cargo.toml"))
+        .arg("--api-base")
+        .arg(&registry.base_url)
+        .arg("doctor")
+        .env("CARGO_HOME", td.path().join("cargo-home"))
+        .env_remove("CARGO_REGISTRY_TOKEN")
+        .env_remove("CARGO_REGISTRIES_CRATES_IO_TOKEN")
+        .assert()
+        .success()
+        .stdout(contains("NONE FOUND"));

     registry.join();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// 4. Doctor reports missing token when not set
#[test]
fn doctor_reports_missing_token() {
let td = tempdir().expect("tempdir");
create_workspace(td.path());
fs::create_dir_all(td.path().join("cargo-home")).expect("mkdir");
let registry = spawn_registry(1);
temp_env::with_vars(
[
("CARGO_REGISTRY_TOKEN", None::<&str>),
("CARGO_REGISTRIES_CRATES_IO_TOKEN", None::<&str>),
],
|| {
shipper_cmd()
.arg("--manifest-path")
.arg(td.path().join("Cargo.toml"))
.arg("--api-base")
.arg(&registry.base_url)
.arg("doctor")
.env("CARGO_HOME", td.path().join("cargo-home"))
.env_remove("CARGO_REGISTRY_TOKEN")
.env_remove("CARGO_REGISTRIES_CRATES_IO_TOKEN")
.assert()
.success()
.stdout(contains("NONE FOUND"));
},
);
registry.join();
}
/// 4. Doctor reports missing token when not set
#[test]
fn doctor_reports_missing_token() {
let td = tempdir().expect("tempdir");
create_workspace(td.path());
fs::create_dir_all(td.path().join("cargo-home")).expect("mkdir");
let registry = spawn_registry(1);
shipper_cmd()
.arg("--manifest-path")
.arg(td.path().join("Cargo.toml"))
.arg("--api-base")
.arg(&registry.base_url)
.arg("doctor")
.env("CARGO_HOME", td.path().join("cargo-home"))
.env_remove("CARGO_REGISTRY_TOKEN")
.env_remove("CARGO_REGISTRIES_CRATES_IO_TOKEN")
.assert()
.success()
.stdout(contains("NONE FOUND"));
registry.join();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/shipper-cli/tests/e2e_doctor.rs` around lines 175 - 206, The test
doctor_reports_missing_token unnecessarily wraps the command in
temp_env::with_vars while the closure only sets variables to None and the
Command already calls env_remove; remove the temp_env::with_vars wrapper and run
the shipper_cmd()... chain directly (keep the same args, .env("CARGO_HOME",
...), .env_remove("CARGO_REGISTRY_TOKEN") and
.env_remove("CARGO_REGISTRIES_CRATES_IO_TOKEN") calls) so the subprocess
environment is controlled only via the Command; update the test body accordingly
and leave spawn_registry(1) / registry.join() unchanged.

Comment thread RELEASE_CHECKLIST_v0.3.0.md Outdated
Comment on lines +21 to +23
- [ ] Publish to crates.io (dry-run first):
```bash
cargo publish -p shipper-types --dry-run

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor formatting: Add blank line before fenced code block.

Per Markdown best practices, fenced code blocks should be surrounded by blank lines for proper rendering across all Markdown parsers.

📝 Proposed fix
 - [ ] Publish to crates.io (dry-run first):
+
   ```bash
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- [ ] Publish to crates.io (dry-run first):
```bash
cargo publish -p shipper-types --dry-run
- [ ] Publish to crates.io (dry-run first):
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 22-22: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@RELEASE_CHECKLIST_v0.3.0.md` around lines 21 - 23, Add a blank line before
the fenced code block that starts with "```bash" so the Markdown renders
correctly; locate the checklist item line "- [ ] Publish to crates.io (dry-run
first):" and insert a single empty line between that paragraph and the following
"```bash" fence to separate the block from the preceding text.

Comment thread RELEASE_CHECKLIST_v0.3.0.md
Comment thread RELEASE_NOTES_v0.3.0.md
Comment on lines +20 to +22
```bash
shipper publish --resume-from my-critical-crate
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor formatting: Add blank line before fenced code block.

Per Markdown best practices, fenced code blocks should be surrounded by blank lines.

📝 Proposed fix
 Interrupted publishes can now be resumed from any specific package, not just the last failed one. This gives you total control over the recovery process.
+
 ```bash
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```bash
shipper publish --resume-from my-critical-crate
```
Interrupted publishes can now be resumed from any specific package, not just the last failed one. This gives you total control over the recovery process.
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 22-22: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@RELEASE_NOTES_v0.3.0.md` around lines 20 - 22, Add a single blank line
immediately before the fenced code block that begins with "```bash" containing
the command "shipper publish --resume-from my-critical-crate" so the code block
is surrounded by blank lines per Markdown best practices; locate the fenced
block in RELEASE_NOTES_v0.3.0.md and insert one empty line above the "```bash"
marker.

@coderabbitai coderabbitai 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.

Review continued from previous batch...

Comment on lines +178 to +180
/// Suppress informational output
#[arg(short, long, global = true)]
quiet: bool,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep warnings visible under --quiet.

Line 178 says --quiet suppresses informational output, but CliReporter::warn is gated on the same flag. With the new CI snippets using shipper publish --quiet, retry/auth/connectivity warnings disappear from logs too. Either keep warnings printing or rename/document the flag as silencing warnings as well.

Possible fix
-    /// Suppress informational output
+    /// Suppress informational output; warnings and errors are still shown
     #[arg(short, long, global = true)]
     quiet: bool,
@@
     fn warn(&mut self, msg: &str) {
-        if !self.quiet {
-            eprintln!("[warn] {msg}");
-        }
+        eprintln!("[warn] {msg}");
     }

Also applies to: 256-270

Comment on lines +450 to +486
for reg in target_registries {
if opts.registries.len() > 1 {
println!(
"\n🚀 Publishing to registry: {} ({})",
reg.name, reg.api_base
);
}

let receipt = engine::run_publish(&planned, &opts, &mut reporter)?;
let mut current_planned = planned.clone();
current_planned.plan.registry = reg.clone();

progress.finish();
let mut current_opts = opts.clone();
// Segregate state dir by registry name if multiple registries
if opts.registries.len() > 1 {
current_opts.state_dir = opts.state_dir.join(&reg.name);
}

print_receipt(
&receipt,
&planned.workspace_root,
&opts.state_dir,
&cli.format,
);
let total_packages = current_planned.plan.packages.len();
let mut progress = ProgressReporter::new(total_packages, cli.quiet);

// Show initial progress if we have packages
if total_packages > 0 {
let first_pkg = &current_planned.plan.packages[0];
progress.set_package(1, &first_pkg.name, &first_pkg.version);
}

let receipt = engine::run_publish(&current_planned, &current_opts, &mut reporter)?;

progress.finish();

print_receipt(
&receipt,
&current_planned.workspace_root,
&current_opts.state_dir,
&cli.format,
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Aggregate multi-registry receipts before printing JSON.

When --format json is combined with more than one target registry, these loops emit a human banner and then a separate pretty-printed receipt per registry. That is not a single valid JSON document, so machine consumers of the CLI output break. Please gate the banners to text mode and emit one JSON array/object for the multi-registry case. A regression test around publish/resume --registries a,b --format json would lock this down.

Also applies to: 495-530

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/shipper-cli/src/main.rs` around lines 450 - 486, The loop over
target_registries currently prints per-registry banners/progress and calls
print_receipt per registry, which breaks JSON output when cli.format == "json";
instead collect each registry's receipt (from engine::run_publish) into a Vec
and, after the loop, if cli.format is json serialize and print a single JSON
array/object (suppress banners/progress during iteration), otherwise keep the
existing human-mode behavior (show banner/progress and call print_receipt per
registry). Apply the same change to the analogous publish/resume loop (the other
target_registries loop that also calls ProgressReporter, engine::run_publish and
print_receipt).

Comment on lines +467 to +479
let total_packages = current_planned.plan.packages.len();
let mut progress = ProgressReporter::new(total_packages, cli.quiet);

// Show initial progress if we have packages
if total_packages > 0 {
let first_pkg = &current_planned.plan.packages[0];
progress.set_package(1, &first_pkg.name, &first_pkg.version);
}

let receipt = engine::run_publish(&current_planned, &current_opts, &mut reporter)?;

progress.finish();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

This progress reporter never advances past the first package.

The only update here is set_package(1, ...) before calling engine::run_publish / engine::run_resume. Because this ProgressReporter instance is never driven again in this function, multi-package runs will sit on package 1 until finish(). Wire it to real publish events/callbacks or drop the progress bar until it can reflect actual state.

Also applies to: 511-523

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/shipper-cli/src/main.rs` around lines 467 - 479, The ProgressReporter
is being preset to package 1 but never updated, so multi-package runs stall;
either remove the pre-call to ProgressReporter::set_package(...) and let the
engine drive progress via the existing reporter, or explicitly wire
ProgressReporter into the publish/resume flow so engine::run_publish and
engine::run_resume (or the reporter callbacks they use) call
ProgressReporter::set_package/update as packages change; locate the
ProgressReporter instantiation and the set_package call near
current_planned/current_opts/reporter and either delete that initial
set_package(1, ...) or pass &mut progress (or register progress callbacks) into
engine::run_publish/run_resume so the progress bar advances for each package
before calling progress.finish().

Comment on lines +549 to +565
let target_registries = if opts.registries.is_empty() {
vec![planned.plan.registry.clone()]
} else {
opts.registries.clone()
};

for reg in target_registries {
if opts.registries.len() > 1 {
println!(
"\n🩺 Diagnostics for registry: {} ({})",
reg.name, reg.api_base
);
}
let mut current_planned = planned.clone();
current_planned.plan.registry = reg;
run_doctor(&current_planned, &opts, &mut reporter)?;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Pass registry-scoped RuntimeOptions into run_doctor.

publish and resume re-root state_dir under state_dir/<registry> when more than one registry is targeted, but Line 564 still calls run_doctor with the original opts. In multi-registry mode that means the permission/path checks run against the shared base directory, not the registry-specific directory the runtime actually uses.

Possible fix
             for reg in target_registries {
                 if opts.registries.len() > 1 {
                     println!(
                         "\n🩺 Diagnostics for registry: {} ({})",
                         reg.name, reg.api_base
                     );
                 }
                 let mut current_planned = planned.clone();
                 current_planned.plan.registry = reg;
-                run_doctor(&current_planned, &opts, &mut reporter)?;
+                let mut current_opts = opts.clone();
+                if opts.registries.len() > 1 {
+                    current_opts.state_dir =
+                        opts.state_dir.join(&current_planned.plan.registry.name);
+                }
+                run_doctor(&current_planned, &current_opts, &mut reporter)?;
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let target_registries = if opts.registries.is_empty() {
vec![planned.plan.registry.clone()]
} else {
opts.registries.clone()
};
for reg in target_registries {
if opts.registries.len() > 1 {
println!(
"\n🩺 Diagnostics for registry: {} ({})",
reg.name, reg.api_base
);
}
let mut current_planned = planned.clone();
current_planned.plan.registry = reg;
run_doctor(&current_planned, &opts, &mut reporter)?;
}
let target_registries = if opts.registries.is_empty() {
vec![planned.plan.registry.clone()]
} else {
opts.registries.clone()
};
for reg in target_registries {
if opts.registries.len() > 1 {
println!(
"\n🩺 Diagnostics for registry: {} ({})",
reg.name, reg.api_base
);
}
let mut current_planned = planned.clone();
current_planned.plan.registry = reg;
let mut current_opts = opts.clone();
if opts.registries.len() > 1 {
current_opts.state_dir =
opts.state_dir.join(&current_planned.plan.registry.name);
}
run_doctor(&current_planned, &current_opts, &mut reporter)?;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/shipper-cli/src/main.rs` around lines 549 - 565, The loop uses the
original opts when calling run_doctor so checks run against the shared state_dir
instead of the registry-scoped directory; create a registry-scoped
RuntimeOptions (clone opts into a new variable, update its state_dir to re-root
under state_dir/<registry> or the same logic used by publish/resume) and pass
that into run_doctor (use the same scope where current_planned.plan.registry is
set). Ensure you reference and update the RuntimeOptions instance used for
runtime checks (opts -> registry_opts) before calling
run_doctor(&current_planned, &registry_opts, &mut reporter).

Comment on lines +1457 to +1475
// Identify all directories to clean (base + any registry subdirs)
let mut dirs_to_clean = vec![abs_state.clone()];
if let Ok(entries) = std::fs::read_dir(&abs_state) {
for entry in entries.flatten() {
if let Ok(file_type) = entry.file_type()
&& file_type.is_dir()
&& entry.file_name() != "cache"
{
dirs_to_clean.push(entry.path());
}
}
}

for dir in dirs_to_clean {
clean_single_dir(&dir, workspace_root, keep_receipt, force)?;
}

println!("Clean complete");
Ok(())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do a lock-validation pass before deleting anything.

run_clean starts mutating directories as it iterates dirs_to_clean. Because the base state directory is always processed first, a later active lock in a registry subdirectory will make the command fail after earlier state was already deleted. Please split this into a read-only lock scan first and a deletion pass second, so clean is all-or-nothing unless --force is set. A tempdir test with an unlocked root and a locked child directory would cover this path.

Also applies to: 1489-1516

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/shipper-cli/src/main.rs` around lines 1457 - 1475, The cleanup
currently deletes directories as it iterates dirs_to_clean which can leave
partial state if a later subdirectory has an active lock; before calling
clean_single_dir, add a read-only lock-validation pass that scans the same
dirs_to_clean (derived from abs_state and registry subdirs) and checks for
active locks (unless force is true), returning an error and aborting before any
mutation if any lock is held; only if the validation passes (or force is set)
proceed to the existing deletion loop that calls
clean_single_dir(workspace_root, keep_receipt, force). Apply the same two-phase
validation-then-delete change for the other block noted around lines 1489-1516.

Comment on lines +1996 to +2035
fn bdd_ci_github_actions_produces_valid_yaml_steps() {
let td = tempdir().expect("tempdir");
create_single_crate_workspace(td.path());

let output = shipper_cmd()
.arg("--manifest-path")
.arg(td.path().join("Cargo.toml"))
.arg("ci")
.arg("github-actions")
.assert()
.success()
.get_output()
.stdout
.clone();

let stdout = String::from_utf8(output).expect("utf8");

// Then: output contains GitHub Actions YAML structure
assert!(
stdout.contains("- name:"),
"expected '- name:' YAML step marker, got: {stdout}"
);
assert!(
stdout.contains("uses:"),
"expected 'uses:' action reference, got: {stdout}"
);
assert!(
stdout.contains("shipper publish"),
"expected 'shipper publish' command reference, got: {stdout}"
);
assert!(
stdout.contains("CARGO_REGISTRY_TOKEN"),
"expected CARGO_REGISTRY_TOKEN env var reference, got: {stdout}"
);
// And: output starts with a comment header
assert!(
stdout.starts_with("# GitHub Actions"),
"expected output to start with '# GitHub Actions' comment, got: {stdout}"
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and its size
wc -l crates/shipper-cli/tests/bdd_workflow.rs

Repository: EffortlessMetrics/shipper

Length of output: 114


🏁 Script executed:

# Read the specific function mentioned in the review (lines 1996-2035)
sed -n '1996,2035p' crates/shipper-cli/tests/bdd_workflow.rs

Repository: EffortlessMetrics/shipper

Length of output: 1435


🏁 Script executed:

# Check the other line ranges mentioned (2766-2802 and 2805-2836)
sed -n '2766,2802p' crates/shipper-cli/tests/bdd_workflow.rs

Repository: EffortlessMetrics/shipper

Length of output: 1247


🏁 Script executed:

sed -n '2805,2836p' crates/shipper-cli/tests/bdd_workflow.rs

Repository: EffortlessMetrics/shipper

Length of output: 1030


🏁 Script executed:

# Check if insta is already a dependency
fd Cargo.toml | xargs grep -l "insta" 2>/dev/null

Repository: EffortlessMetrics/shipper

Length of output: 1143


🏁 Script executed:

# Check the workspace root Cargo.toml and shipper-cli Cargo.toml for dependencies
cat crates/shipper-cli/Cargo.toml

Repository: EffortlessMetrics/shipper

Length of output: 1962


Replace substring assertions with snapshot testing using insta.

The current assertions will not catch structural regressions in CI templates. Validate YAML validity once, then snapshot the full output so CI template changes remain intentional.

Replace the multiple assert!(stdout.contains(...)) checks with a YAML validity check and insta::assert_snapshot!(). This aligns with the coding guideline to use snapshot testing with insta for test assertions.

Example refactor
 let stdout = String::from_utf8(output).expect("utf8");
-assert!(stdout.contains("- name:"));
-assert!(stdout.contains("uses:"));
-assert!(stdout.contains("shipper publish"));
-assert!(stdout.contains("CARGO_REGISTRY_TOKEN"));
+let _: serde_yaml::Value = serde_yaml::from_str(&stdout).expect("valid YAML");
+insta::assert_snapshot!("github_actions_ci_template", stdout);

Also applies to the GitLab (lines 2766-2802) and CircleCI (lines 2805-2836) template tests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/shipper-cli/tests/bdd_workflow.rs` around lines 1996 - 2035, In
bdd_ci_github_actions_produces_valid_yaml_steps replace the multiple substring
asserts with a YAML validity check and an insta snapshot assertion: parse stdout
as YAML to ensure it's valid, then call insta::assert_snapshot!() on the full
stdout to capture the template; update the test to import/enable insta and any
YAML parser you already use (or serde_yaml) and apply the same refactor to the
GitLab and CircleCI tests (functions around lines noted in the review) so
structural regressions are caught by snapshots instead of simple contains()
checks.

…klist

- Increase mock server recv_timeout from 5s to 30s across all test files
  to prevent flaky failures under cargo llvm-cov instrumentation
- Add deterministic timeout test via fake cargo shim (shipper-cargo)
- Normalize git_commit/git_branch/git_dirty in cli_e2e doctor snapshots
  so they don't break after every commit
- Correct release checklist publish order with layered dependencies
- Narrow .gitignore *.log to test_full.log
@EffortlessSteven EffortlessSteven merged commit 5d8b1c8 into main Mar 8, 2026
10 of 14 checks passed
@EffortlessSteven EffortlessSteven deleted the release/v0.3.0-rc.1 branch March 8, 2026 02:50
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.

2 participants