feat(packaging): create shipper-core library crate (#95 PR 1)#140
Conversation
Reworking #95 into the three-crate split the user specified originally: shipper (installable facade) -> shipper-cli (real CLI adapter) [PR 2] -> shipper-core (pure library/engine) [THIS PR] This PR is the bottom layer only. It moves every non-CLI module out of `crates/shipper/src/` into a new `crates/shipper-core/` crate and leaves `shipper` as a thin re-export facade so the rest of the workspace (shipper-cli, integration tests, downstream code) keeps compiling unchanged. ## Why this reopens #95 #135 merged a different shape: `shipper` carried the lib + bin, with `shipper-cli` as a 9-line compat shim. That conflated engine code and CLI code in one crate, left `shipper-cli` as a shim with no value, and didn't match the design goal of a no-clap library surface. The three-crate split below is what was originally asked for. ## What moved All moved via `git mv` so history is preserved: - `crates/shipper/src/{engine,state,ops,plan,runtime}/` → shipper-core - `crates/shipper/src/{config,encryption,git,types,webhook, property_tests,stress_tests}.rs` → shipper-core - `crates/shipper/src/lib.rs` → shipper-core (then edited to drop the `pub mod cli;` declaration and retarget docs at `shipper_core`) - proptest-regressions fixtures moved with their owning tests What stayed in `crates/shipper/`: - `src/cli/` (moves to `shipper-cli` in PR 2) - `src/bin/shipper.rs` (stays — this crate owns the installable binary) ## Facade The new `crates/shipper/src/lib.rs` re-exports shipper-core's public surface (auth, cargo, cargo_failure, config, encryption, engine, git, lock, plan, registry, retry, runtime, state, store, types, webhook) so every existing `shipper::X` import path still resolves. PR 3 slims this to a curated re-export list once `shipper-cli` takes over the CLI. ## Snapshots 326 insta snapshots renamed `shipper__*.snap` → `shipper_core__*.snap` to match the new crate prefix in `module_path!()`. Tests pass with the renamed files; no content changes. ## CI Architecture-guard workflow updated to watch `crates/shipper-core/src/**` (where the layer dirs now live) in addition to `crates/shipper/src/**`, so the upward-import check keeps enforcing instead of silently skipping. ## Verification - `cargo check --workspace --all-targets` — clean - `cargo clippy --workspace --all-targets --all-features -- -D warnings` — clean - `cargo test -p shipper-core --lib` — 1889 passed - `cargo test -p shipper` — 39 passed - Full workspace test run — all passed except pre-existing Windows- specific flakes (`concurrent_version_exists_checks` on macOS, `preflight_command_*` temp-dir locking on Windows); both pass in isolation and are unrelated to this split. ## What this PR does not do - Does not move the CLI (that's PR 2) - Does not slim `crates/shipper`'s Cargo.toml or README (PR 3) - Does not touch workspace-wide docs or how-to guides (PR 4)
Summary by CodeRabbit
WalkthroughA new Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces the shipper-core crate, which centralizes the engine, planning, and state logic previously distributed across microcrates as part of a decrating effort. The implementation includes the crate's manifest, documentation, and public API surface. Review feedback points out the use of non-existent dependency versions in Cargo.toml and suggests a more idiomatic approach for module re-exports in lib.rs by replacing the #[path] attribute with a standard pub use statement.
| anyhow = "1.0.102" | ||
| thiserror = "2.0.18" | ||
| cargo_metadata = "0.23.1" | ||
| serde = { version = "1.0.228", features = ["derive"] } | ||
| serde_json = "1.0.149" | ||
| serde_with = "3.17.0" | ||
| reqwest = { version = "0.13.2", features = ["blocking", "json", "rustls"] } | ||
| rand = { version = "0.10.1", features = ["std"] } | ||
| chrono = { version = "0.4.44", features = ["serde"] } | ||
| humantime = "2.3.0" | ||
| which = "8.0" | ||
| toml = "1.0.3" | ||
| dirs = "6.0" | ||
| hex = "0.4.3" | ||
| gethostname = "1.1.0" | ||
| tokio = { version = "1.42", features = ["rt", "time", "sync", "macros"] } | ||
| base64 = "0.22" | ||
| aes-gcm = "0.10" | ||
| pbkdf2 = { version = "0.12", features = ["simple"] } | ||
|
|
||
| # HMAC for webhook signatures | ||
| hmac = "0.13" | ||
| sha2 = "0.11" | ||
|
|
||
| [lints] | ||
| workspace = true | ||
|
|
||
| [dev-dependencies] | ||
| tempfile = "3.26.0" | ||
| insta = { version = "1.46.3", features = ["yaml"] } | ||
| proptest = "1.10.0" | ||
| serial_test = "3.4.0" | ||
| tiny_http = "0.12.0" | ||
| temp-env = "0.3" | ||
| console = "0.16.3" |
There was a problem hiding this comment.
Several dependency versions specified here (e.g., reqwest 0.13.2, tempfile 3.26.0, rand 0.10.1, insta 1.46.3) appear to be non-existent or significantly ahead of current stable releases on crates.io. For instance, reqwest is currently at 0.12.x and tempfile is at 3.16.x. Using hallucinated or future versions will cause compilation failures in standard environments. Please verify and use the correct latest stable versions from crates.io.
| #[path = "state/store/mod.rs"] | ||
| pub mod store; |
There was a problem hiding this comment.
Using #[path] to define a top-level module from a nested directory is non-idiomatic in modern Rust and can be confusing for both developers and tooling (like IDEs or rust-analyzer). Since state is already a public module, it is better to define the store module naturally within src/state/mod.rs and then use a pub use statement in lib.rs to provide the desired public API surface.
| #[path = "state/store/mod.rs"] | |
| pub mod store; | |
| pub use crate::state::store; |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Three doctests in `ops/lock/mod.rs` still used `use shipper::lock::LockFile;` after the #95 PR 1 split. Doctests compile as external code against the crate being tested, so the crate name now has to be `shipper_core`. Caught by the `cargo test --workspace --doc` lane in CI (not covered by `cargo test -p shipper-core --lib` locally).
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/architecture-guard.yml:
- Around line 7-13: The workflow still lists the stale trigger path
'crates/shipper/src/**' but all guarded scans (ops, runtime, state, plan) are
now under 'crates/shipper-core/src/**', so remove the two occurrences of
'crates/shipper/src/**' from the workflow trigger paths (both push.paths
entries) to stop CI noise; alternatively, if you want to keep defending that
location, add corresponding scan entries for 'crates/shipper/src/' so the guards
will actually run.
In `@crates/shipper-core/Cargo.toml`:
- Around line 28-50: Move shared external dependencies (e.g., anyhow, serde,
serde_json, chrono, tokio, reqwest, etc.) out of this crate's Cargo.toml and
declare them centrally under [workspace.dependencies]; in addition set
serde.workspace = true in this crate (and other workspace crates using serde) so
they inherit the workspace version; remove the duplicated per-crate entries from
the current dependencies list and rely on the workspace-managed entries to
prevent version drift and duplicate copies in Cargo.lock.
In `@crates/shipper-core/README.md`:
- Around line 23-24: Update the README link to point to the crate-specific
changelog (change the linked path from `CHANGELOG.md` to
`crates/shipper-core/CHANGELOG.md`) so users see shipper-core breaking changes
only, and normalize repository owner casing to match `effortlessmetrics/shipper`
used in `crates/shipper-core/src/lib.rs` (make the README use
`effortlessmetrics/shipper`) to keep references consistent; verify the link
resolves and adjust any other occurrences in `README.md` or
`crates/shipper-core/src/lib.rs` for consistent casing.
In `@crates/shipper-core/src/lib.rs`:
- Around line 83-86: The intra-doc links ([lock], [store], [cargo],
[cargo_failure]) point to items re-exported via `pub use` and may not resolve
when cross-crate re-exports are in play; update the comment to use
fully-qualified paths for crate-local modules (e.g. [`crate::lock`],
[`crate::store`], [`crate::cargo`]) and for the cross-crate re-export use the
original crate name or fully-qualified external path (e.g.
[`shipper_cargo_failure`] or [`shipper_cargo_failure::...`]) so rustdoc can
resolve them reliably, then run `cargo doc -p shipper-core --no-deps` to confirm
no "unresolved link" warnings.
- Around line 153-161: The crate root currently mounts the store module via
#[path = "state/store/mod.rs"] pub mod store, which conflicts with the
documented architecture; instead, declare the module under the state module and
re-export it from the crate root: add pub(crate) mod store; to the state module
(state::mod) so the implementation lives at state::store, then remove the #[path
= ...] pub mod store declaration from lib.rs and replace it with a re-export
like pub use crate::state::store; so callers keep crate::store while the module
lives under state::store as intended.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 0146297a-7a28-43f7-9792-bb861c037477
⛔ Files ignored due to path filters (233)
Cargo.lockis excluded by!**/*.lockcrates/shipper-core/src/engine/parallel/snapshots/shipper_core__engine__parallel__tests__snapshot_tests__snapshot_chunk_by_max_concurrent_basic.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/parallel/snapshots/shipper_core__engine__parallel__tests__snapshot_tests__snapshot_chunk_by_max_concurrent_empty.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/parallel/snapshots/shipper_core__engine__parallel__tests__snapshot_tests__snapshot_chunk_by_max_concurrent_larger_than_items.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/parallel/snapshots/shipper_core__engine__parallel__tests__snapshot_tests__snapshot_chunk_by_max_concurrent_one.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/parallel/snapshots/shipper_core__engine__parallel__tests__snapshot_tests__snapshot_execution_plan_diamond.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/parallel/snapshots/shipper_core__engine__parallel__tests__snapshot_tests__snapshot_execution_plan_independent_forest.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/parallel/snapshots/shipper_core__engine__parallel__tests__snapshot_tests__snapshot_execution_plan_linear_chain.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/parallel/snapshots/shipper_core__engine__parallel__tests__snapshot_tests__snapshot_execution_plan_wide_fan_out.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/parallel/snapshots/shipper_core__engine__parallel__tests__snapshot_tests__snapshot_parallel_config_default.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/parallel/snapshots/shipper_core__engine__parallel__tests__snapshot_tests__snapshot_policy_effects_balanced.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/parallel/snapshots/shipper_core__engine__parallel__tests__snapshot_tests__snapshot_policy_effects_fast.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/parallel/snapshots/shipper_core__engine__parallel__tests__snapshot_tests__snapshot_policy_effects_safe.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/snapshots/shipper_core__engine__tests__error_classification_matrix.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/snapshots/shipper_core__engine__tests__init_state_multi_package.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/snapshots/shipper_core__engine__tests__init_state_single_package.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/snapshots/shipper_core__engine__tests__sm_receipt_multi_package.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/snapshots/shipper_core__engine__tests__sm_state_partial_failure.snapis excluded by!**/*.snapcrates/shipper-core/src/engine/snapshots/shipper_core__engine__tests__state_after_mixed_transitions.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__credentials__tests__error_message_snapshots__error_msg_credentials_not_found.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__credentials__tests__error_message_snapshots__error_msg_empty_credentials.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__credentials__tests__error_message_snapshots__error_msg_malformed_toml.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__credentials__tests__error_message_snapshots__error_msg_token_wrong_registry.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__credentials__tests__snapshots__snapshot_credentials_crates_io_registry_section.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__credentials__tests__snapshots__snapshot_credentials_custom_registry_section.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__credentials__tests__snapshots__snapshot_credentials_file_with_all_formats.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__credentials__tests__snapshots__snapshot_credentials_legacy_toplevel_format.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__credentials__tests__snapshots__snapshot_error_malformed_credentials_file.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__credentials__tests__snapshots__snapshot_error_missing_credentials_file.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__credentials__tests__snapshots__snapshot_error_token_not_found_for_registry.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__credentials__tests__snapshots__snapshot_list_configured_registries_empty.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__credentials__tests__snapshots__snapshot_list_configured_registries_mixed.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__credentials__tests__snapshots__snapshot_list_registries_many.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__edge_snapshots__snapshot_auth_info_not_detected.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__edge_snapshots__snapshot_auth_info_with_credentials_file.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__edge_snapshots__snapshot_auth_info_with_env_default.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__edge_snapshots__snapshot_auth_info_with_env_registry.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__edge_snapshots__snapshot_mask_token_long_value.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__edge_snapshots__snapshot_mask_token_short_values.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__edge_snapshots__snapshot_resolve_unicode_token.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__edge_snapshots__snapshot_resolve_whitespace_token.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__edge_snapshots__snapshot_token_source_credentials_file.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__edge_snapshots__snapshot_token_source_env_default.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__edge_snapshots__snapshot_token_source_env_registry.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__edge_snapshots__snapshot_token_source_none.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__error_message_snapshots__error_msg_missing_token.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__error_message_snapshots__error_msg_token_source_none.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__snapshots__snapshot_auth_info_default.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__snapshots__snapshot_resolve_token_custom_registry_from_credentials.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__snapshots__snapshot_resolve_token_from_credentials_file.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__snapshots__snapshot_resolve_token_from_env_default.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__snapshots__snapshot_resolve_token_from_env_registry.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/auth/snapshots/shipper_core__ops__auth__resolver__tests__snapshots__snapshot_resolve_token_none_found.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/cargo/snapshots/shipper_core__ops__cargo__tests__snapshot_tests_absorbed__snapshot_invalid_package_names.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/cargo/snapshots/shipper_core__ops__cargo__tests__snapshot_tests_absorbed__snapshot_package_info_prerelease_version.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/cargo/snapshots/shipper_core__ops__cargo__tests__snapshot_tests_absorbed__snapshot_package_info_simple.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/cargo/snapshots/shipper_core__ops__cargo__tests__snapshot_tests_absorbed__snapshot_package_info_with_registries.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/cargo/snapshots/shipper_core__ops__cargo__tests__snapshot_tests_absorbed__snapshot_valid_package_names.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__clean_result_err_not_git.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__clean_result_err_uncommitted.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__clean_result_ok.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__context_all_fields.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__context_detached_head.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__context_dirty_detached.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__context_dirty_with_tag.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__context_feature_branch_clean.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__context_no_commit_no_branch.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__context_tagged_detached.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__is_dirty_all_variants.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__porcelain_empty_output.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__porcelain_parsed_files.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__porcelain_renamed_and_copied.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__porcelain_spaces_in_names.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__short_commit_empty_string.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__edge_case_snapshots__short_commit_exactly_eight.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__clean_working_tree.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__dirty_default_behavior.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__dirty_working_tree.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__ensure_git_clean_error.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__git_context_commit_only.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__git_context_dirty_no_tag.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__git_context_empty.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__git_context_full.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__git_rev_parse_failed_error.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__git_status_failed_error.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__short_commit_full_hash.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__short_commit_none.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__short_commit_seven_chars.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__short_commit_short_hash.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__tag_absent.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__tag_dirty_tree.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__tag_prerelease.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__tag_semver.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/git/snapshots/shipper_core__ops__git__context__snapshot_tests__unknown_dirty_state.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__edge_case_tests__lock_info_all_fields.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__edge_case_tests__lock_info_no_plan_id.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__edge_case_tests__lock_state_corrupt.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__edge_case_tests__lock_state_locked_no_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__edge_case_tests__lock_state_locked_with_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__edge_case_tests__lock_state_stale.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__edge_case_tests__lock_state_unlocked.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__hardened_tests__lock_file_after_set_plan_id.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__hardened_tests__lock_info_empty_plan_id.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__hardened_tests__lock_info_json_key_order.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__snapshot_tests__error_corrupt_lock_file_prefix.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__snapshot_tests__error_fresh_lock_with_timeout_prefix.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__snapshot_tests__error_lock_already_held.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__snapshot_tests__error_lock_already_held_with_plan_id.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__snapshot_tests__error_read_nonexistent_lock_prefix.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__snapshot_tests__error_set_plan_id_after_release_prefix.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__snapshot_tests__lock_file_content_with_plan_id.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__snapshot_tests__lock_file_content_without_plan_id.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__snapshot_tests__lock_file_on_disk.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__snapshot_tests__lock_info_debug.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__snapshot_tests__lock_info_yaml.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__snapshot_tests__lock_path_with_root_filename.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__snapshot_tests__lock_path_without_root.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__snapshot_tests__lock_status_locked.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/lock/snapshots/shipper_core__ops__lock__snapshot_tests__lock_status_unlocked.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/process/snapshots/shipper_core__ops__process__snapshot_tests__command_output_failure.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/process/snapshots/shipper_core__ops__process__snapshot_tests__command_output_json_format.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/process/snapshots/shipper_core__ops__process__snapshot_tests__command_output_success.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/process/snapshots/shipper_core__ops__process__snapshot_tests__command_output_timed_out.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/process/snapshots/shipper_core__ops__process__snapshot_tests__command_result_failure_no_exit_code.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/process/snapshots/shipper_core__ops__process__snapshot_tests__command_result_failure_with_exit_code.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/process/snapshots/shipper_core__ops__process__snapshot_tests__command_result_json_format.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/process/snapshots/shipper_core__ops__process__snapshot_tests__command_result_success.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/process/snapshots/shipper_core__ops__process__snapshot_tests__command_result_with_multiline_output.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/process/snapshots/shipper_core__ops__process__snapshot_tests__error_message_empty_stderr.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/process/snapshots/shipper_core__ops__process__snapshot_tests__error_message_with_exit_code.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/process/snapshots/shipper_core__ops__process__snapshot_tests__error_message_without_exit_code.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/process/snapshots/shipper_core__ops__process__snapshot_tests__snapshot_publish_args_basic.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/process/snapshots/shipper_core__ops__process__snapshot_tests__snapshot_publish_args_full_flags.snapis excluded by!**/*.snapcrates/shipper-core/src/ops/process/snapshots/shipper_core__ops__process__snapshot_tests__snapshot_publish_args_with_registry_and_no_verify.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_1000_items_by_10.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_10_items_by_5.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_13_items_by_4_with_remainder.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_5_by_2.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_6_by_3.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_7_items_by_3.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_empty.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_exact_fit.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_large_list_by_7.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_max_concurrent_1.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_max_concurrent_usize_max.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_max_concurrent_zero.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_prime_37_by_6.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_single_item.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_single_item_max_one.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_chunk_size_1_with_4_items.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/chunking/snapshots/shipper_core__plan__chunking__snapshot_tests__snapshot_dependency_like_items_by_2.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/levels/snapshots/shipper_core__plan__levels__snapshot_tests__snapshot_all_independent.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/levels/snapshots/shipper_core__plan__levels__snapshot_tests__snapshot_cycle_fallback.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/levels/snapshots/shipper_core__plan__levels__snapshot_tests__snapshot_deep_binary_tree.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/levels/snapshots/shipper_core__plan__levels__snapshot_tests__snapshot_diamond_dependency.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/levels/snapshots/shipper_core__plan__levels__snapshot_tests__snapshot_disconnected_components.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/levels/snapshots/shipper_core__plan__levels__snapshot_tests__snapshot_double_diamond.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/levels/snapshots/shipper_core__plan__levels__snapshot_tests__snapshot_empty_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/levels/snapshots/shipper_core__plan__levels__snapshot_tests__snapshot_linear_chain.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/levels/snapshots/shipper_core__plan__levels__snapshot_tests__snapshot_wide_fan_out.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__build_dep_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__deep_chain_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__dev_dep_cycle_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__diamond_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__double_diamond_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__double_diamond_selected_mid.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__empty_workspace_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__error_cycle_detection.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__error_msg_cycle_detection.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__error_msg_missing_manifest.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__error_msg_non_publishable_dep.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__error_msg_selecting_non_publishable.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__error_msg_unknown_selected_package.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__error_non_publishable_dep.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__error_selecting_non_publishable.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__error_unknown_package.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__linear_chain_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__mixed_dep_kinds_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__multi_crate_plan_with_deps.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__multi_select_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__package_selection_b.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__plan_summary_display.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__prerelease_versions_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__single_crate_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__skipped_packages_detail.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__special_names_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/plan/snapshots/shipper_core__plan__tests__wide_flat_plan_5.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/environment/snapshots/shipper_core__runtime__environment__snapshot_tests__ci_environment_all_debug_variants.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/environment/snapshots/shipper_core__runtime__environment__snapshot_tests__ci_environment_all_display_variants.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/environment/snapshots/shipper_core__runtime__environment__snapshot_tests__ci_environment_serialization.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/environment/snapshots/shipper_core__runtime__environment__snapshot_tests__environment_fingerprint_prerelease_version.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/environment/snapshots/shipper_core__runtime__environment__snapshot_tests__environment_fingerprint_structured.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/environment/snapshots/shipper_core__runtime__environment__snapshot_tests__environment_fingerprint_structured_unknown_versions.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/environment/snapshots/shipper_core__runtime__environment__snapshot_tests__environment_info_fingerprint_github_actions_with_vars.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/environment/snapshots/shipper_core__runtime__environment__snapshot_tests__environment_info_fingerprint_local_no_vars.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/environment/snapshots/shipper_core__runtime__environment__snapshot_tests__environment_info_fingerprint_string_ci_with_vars.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/environment/snapshots/shipper_core__runtime__environment__snapshot_tests__environment_info_fingerprint_string_local.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/environment/snapshots/shipper_core__runtime__environment__snapshot_tests__environment_info_gitlab_ci.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/environment/snapshots/shipper_core__runtime__environment__snapshot_tests__environment_info_many_env_vars.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/environment/snapshots/shipper_core__runtime__environment__snapshot_tests__environment_info_windows_aarch64.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_backoff_constant_sequence.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_backoff_exponential_clamped.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_backoff_exponential_sequence.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_backoff_immediate_sequence.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_backoff_linear_sequence.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_classify_already_uploaded.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_classify_auth_denied.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_classify_empty_output.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_classify_network_reset.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_classify_network_timeout.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_classify_rate_limit.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_classify_unknown_error.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_execution_state_empty_packages.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_execution_state_single_ambiguous.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_execution_state_single_failed.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_execution_state_single_pending.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_execution_state_single_published.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_execution_state_single_skipped.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_execution_state_single_uploaded.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_retry_config_constant.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_retry_config_exponential.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_retry_config_immediate.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_retry_config_linear.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_state_ambiguous_resolved.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_state_failure_flow.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_state_multi_package_mixed_outcomes.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_state_skip_flow.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_state_success_flow.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_transition_all_skipped_plan.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_transition_ambiguous_to_published.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_transition_pending_to_failed_retry_to_published.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/execution/snapshots/shipper_core__runtime__execution__tests__snapshots__snapshot_transition_pending_to_uploaded_to_published.snapis excluded by!**/*.snapcrates/shipper-core/src/runtime/policy/snapshots/shipper_core__runtime__policy__serialization_snapshot_tests__all_policy_kinds.snapis excluded by!**/*.snap
📒 Files selected for processing (67)
.github/workflows/architecture-guard.ymlCargo.tomlcrates/shipper-core/Cargo.tomlcrates/shipper-core/README.mdcrates/shipper-core/proptest-regressions/property_tests.txtcrates/shipper-core/proptest-regressions/types.txtcrates/shipper-core/src/config.rscrates/shipper-core/src/encryption.rscrates/shipper-core/src/engine/CLAUDE.mdcrates/shipper-core/src/engine/fix_forward.rscrates/shipper-core/src/engine/mod.rscrates/shipper-core/src/engine/parallel/CLAUDE.mdcrates/shipper-core/src/engine/parallel/mod.rscrates/shipper-core/src/engine/parallel/policy.rscrates/shipper-core/src/engine/parallel/publish.rscrates/shipper-core/src/engine/parallel/readiness.rscrates/shipper-core/src/engine/parallel/reconcile.rscrates/shipper-core/src/engine/parallel/tests.rscrates/shipper-core/src/engine/parallel/webhook.rscrates/shipper-core/src/engine/plan_yank.rscrates/shipper-core/src/git.rscrates/shipper-core/src/lib.rscrates/shipper-core/src/ops/CLAUDE.mdcrates/shipper-core/src/ops/auth/CLAUDE.mdcrates/shipper-core/src/ops/auth/credentials.rscrates/shipper-core/src/ops/auth/mod.rscrates/shipper-core/src/ops/auth/oidc.rscrates/shipper-core/src/ops/auth/resolver.rscrates/shipper-core/src/ops/cargo/CLAUDE.mdcrates/shipper-core/src/ops/cargo/mod.rscrates/shipper-core/src/ops/git/CLAUDE.mdcrates/shipper-core/src/ops/git/bin_override.rscrates/shipper-core/src/ops/git/cleanliness.rscrates/shipper-core/src/ops/git/context.rscrates/shipper-core/src/ops/git/mod.rscrates/shipper-core/src/ops/lock/CLAUDE.mdcrates/shipper-core/src/ops/lock/mod.rscrates/shipper-core/src/ops/mod.rscrates/shipper-core/src/ops/process/CLAUDE.mdcrates/shipper-core/src/ops/process/cargo.rscrates/shipper-core/src/ops/process/cross_platform_edge_case_tests.rscrates/shipper-core/src/ops/process/mod.rscrates/shipper-core/src/ops/process/run.rscrates/shipper-core/src/ops/process/snapshot_tests.rscrates/shipper-core/src/ops/process/tests.rscrates/shipper-core/src/ops/process/timeout.rscrates/shipper-core/src/ops/process/types.rscrates/shipper-core/src/ops/process/which.rscrates/shipper-core/src/ops/storage/CLAUDE.mdcrates/shipper-core/src/ops/storage/mod.rscrates/shipper-core/src/plan/CLAUDE.mdcrates/shipper-core/src/plan/chunking/CLAUDE.mdcrates/shipper-core/src/plan/chunking/mod.rscrates/shipper-core/src/plan/levels/CLAUDE.mdcrates/shipper-core/src/plan/levels/mod.rscrates/shipper-core/src/plan/mod.rscrates/shipper-core/src/property_tests.rscrates/shipper-core/src/runtime/CLAUDE.mdcrates/shipper-core/src/runtime/environment/CLAUDE.mdcrates/shipper-core/src/runtime/environment/ci.rscrates/shipper-core/src/runtime/environment/fingerprint.rscrates/shipper-core/src/runtime/environment/mod.rscrates/shipper-core/src/runtime/execution/CLAUDE.mdcrates/shipper-core/src/runtime/execution/mod.rscrates/shipper-core/src/runtime/mod.rscrates/shipper-core/src/runtime/policy/CLAUDE.mdcrates/shipper-core/src/runtime/policy/mod.rs
| - 'crates/shipper-core/src/**' | ||
| - 'crates/shipper/src/**' | ||
| push: | ||
| branches: [main] | ||
| paths: | ||
| - 'crates/shipper-core/src/**' | ||
| - 'crates/shipper/src/**' |
There was a problem hiding this comment.
Trigger path crates/shipper/src/** no longer matches any guarded scan.
After the move, all four scanned layers (ops, runtime, state, plan) live exclusively under crates/shipper-core/src/. The trigger paths still include crates/shipper/src/** (lines 8 and 13), so the workflow will run on CLI-only changes but find nothing to check (all four "layer not scaffolded" guards will skip). Either drop the crates/shipper/src/** triggers, or keep them and add scans against crates/shipper/src/ if you want to defend against the layers being re-introduced there. Currently it's just CI noise.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/architecture-guard.yml around lines 7 - 13, The workflow
still lists the stale trigger path 'crates/shipper/src/**' but all guarded scans
(ops, runtime, state, plan) are now under 'crates/shipper-core/src/**', so
remove the two occurrences of 'crates/shipper/src/**' from the workflow trigger
paths (both push.paths entries) to stop CI noise; alternatively, if you want to
keep defending that location, add corresponding scan entries for
'crates/shipper/src/' so the guards will actually run.
| anyhow = "1.0.102" | ||
| thiserror = "2.0.18" | ||
| cargo_metadata = "0.23.1" | ||
| serde = { version = "1.0.228", features = ["derive"] } | ||
| serde_json = "1.0.149" | ||
| serde_with = "3.17.0" | ||
| reqwest = { version = "0.13.2", features = ["blocking", "json", "rustls"] } | ||
| rand = { version = "0.10.1", features = ["std"] } | ||
| chrono = { version = "0.4.44", features = ["serde"] } | ||
| humantime = "2.3.0" | ||
| which = "8.0" | ||
| toml = "1.0.3" | ||
| dirs = "6.0" | ||
| hex = "0.4.3" | ||
| gethostname = "1.1.0" | ||
| tokio = { version = "1.42", features = ["rt", "time", "sync", "macros"] } | ||
| base64 = "0.22" | ||
| aes-gcm = "0.10" | ||
| pbkdf2 = { version = "0.12", features = ["simple"] } | ||
|
|
||
| # HMAC for webhook signatures | ||
| hmac = "0.13" | ||
| sha2 = "0.11" |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider routing external deps through [workspace.dependencies].
Many of these (anyhow, serde, serde_json, chrono, tokio, reqwest, tempfile, insta, proptest, etc.) are likely already used by other crates in the workspace. Pinning them per-crate makes version drift easy and produces duplicate copies in Cargo.lock if they get out of sync. Promoting them to [workspace.dependencies] and using serde.workspace = true (as already done for the intra-workspace crates above) would centralize version management. Out of scope for this PR if other crates haven't migrated either, but worth tracking.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@crates/shipper-core/Cargo.toml` around lines 28 - 50, Move shared external
dependencies (e.g., anyhow, serde, serde_json, chrono, tokio, reqwest, etc.) out
of this crate's Cargo.toml and declare them centrally under
[workspace.dependencies]; in addition set serde.workspace = true in this crate
(and other workspace crates using serde) so they inherit the workspace version;
remove the duplicated per-crate entries from the current dependencies list and
rely on the workspace-managed entries to prevent version drift and duplicate
copies in Cargo.lock.
| Pre-1.0. The public API will move; breaking changes are called out in | ||
| [`CHANGELOG.md`](https://github.com/EffortlessMetrics/shipper/blob/main/CHANGELOG.md). |
There was a problem hiding this comment.
Verify the CHANGELOG link target.
The README links to a repo-root CHANGELOG.md. If the per-crate API changes are tracked in a crates/shipper-core/CHANGELOG.md (as suggested by the PR description's "points users to crates/shipper-core/CHANGELOG.md for breaking-change tracking"), consider linking there instead so embedders see only shipper-core breaking changes rather than the full repo changelog.
Also note a minor inconsistency: crates/shipper-core/src/lib.rs line 92 uses effortlessmetrics/shipper (lowercase) while this README uses EffortlessMetrics/shipper. GitHub resolves both, but consistency is preferable.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@crates/shipper-core/README.md` around lines 23 - 24, Update the README link
to point to the crate-specific changelog (change the linked path from
`CHANGELOG.md` to `crates/shipper-core/CHANGELOG.md`) so users see shipper-core
breaking changes only, and normalize repository owner casing to match
`effortlessmetrics/shipper` used in `crates/shipper-core/src/lib.rs` (make the
README use `effortlessmetrics/shipper`) to keep references consistent; verify
the link resolves and adjust any other occurrences in `README.md` or
`crates/shipper-core/src/lib.rs` for consistent casing.
| //! - [`lock`] — Distributed lock to prevent concurrent publishes | ||
| //! - [`store`] — `StateStore` trait for pluggable persistence backends | ||
| //! - [`cargo`] — Workspace metadata via `cargo_metadata` | ||
| //! - [`cargo_failure`] — Cargo publish failure classification heuristics |
There was a problem hiding this comment.
Doc intra-links to re-exports may not resolve.
[lock], [store], [cargo], and [cargo_failure] in the module list reference items introduced via pub use rather than pub mod. Rustdoc generally resolves these, but with cross-crate re-exports (pub use shipper_cargo_failure as cargo_failure;) the link target depends on how rustdoc inlines the re-export. Worth running cargo doc -p shipper-core --no-deps and checking for "unresolved link" warnings before publish.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@crates/shipper-core/src/lib.rs` around lines 83 - 86, The intra-doc links
([lock], [store], [cargo], [cargo_failure]) point to items re-exported via `pub
use` and may not resolve when cross-crate re-exports are in play; update the
comment to use fully-qualified paths for crate-local modules (e.g.
[`crate::lock`], [`crate::store`], [`crate::cargo`]) and for the cross-crate
re-export use the original crate name or fully-qualified external path (e.g.
[`shipper_cargo_failure`] or [`shipper_cargo_failure::...`]) so rustdoc can
resolve them reliably, then run `cargo doc -p shipper-core --no-deps` to confirm
no "unresolved link" warnings.
| pub mod state; | ||
|
|
||
| /// `StateStore` trait for pluggable persistence backends. | ||
| /// | ||
| /// Absorbed from the former `shipper-store` microcrate. The implementation | ||
| /// now lives under `state/store/` to reflect the layered architecture; | ||
| /// the public path `shipper::store` is preserved for backward compatibility. | ||
| #[path = "state/store/mod.rs"] | ||
| pub mod store; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm whether state/mod.rs already declares store as a child module.
fd -t f 'mod.rs' crates/shipper-core/src/state -d 2 -x cat {}
echo "---"
rg -nP '\bmod\s+store\b' crates/shipper-core/src/state/
echo "--- any state items in the store impl? ---"
rg -nP 'use\s+(crate|super)::' crates/shipper-core/src/state/store/ | head -40Repository: EffortlessMetrics/shipper
Length of output: 20411
Module structure misalignment: store is declared at crate root but documented as living under state/.
The file crates/shipper-core/src/state/store/mod.rs physically resides in the state directory tree, and the doc comment claims "the implementation now lives under state/store/" — but state/mod.rs does not declare pub mod store;. Instead, store is mounted only at the crate root via #[path = "state/store/mod.rs"].
While this does not currently cause a double-compile (since state/mod.rs does not redeclare store), it creates a maintenance hazard and documentation inconsistency:
- The doc comment and file location strongly suggest
state::storeis the canonical path CLAUDE.md(line 28) states the intent: exposepub(crate) mod store;from state to makecrate::state::storeusable- But the actual module tree does not reflect this structure
Either add pub mod store; to state/mod.rs and remove the #[path] declaration to align with the documented architecture, or update the doc comment and move/rename the file location to reflect that store is a crate-root module.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@crates/shipper-core/src/lib.rs` around lines 153 - 161, The crate root
currently mounts the store module via #[path = "state/store/mod.rs"] pub mod
store, which conflicts with the documented architecture; instead, declare the
module under the state module and re-export it from the crate root: add
pub(crate) mod store; to the state module (state::mod) so the implementation
lives at state::store, then remove the #[path = ...] pub mod store declaration
from lib.rs and replace it with a re-export like pub use crate::state::store; so
callers keep crate::store while the module lives under state::store as intended.
Summary
Reworking #95 into the three-crate split originally specified:
This PR is the bottom layer only. It moves every non-CLI module out of
crates/shipper/src/into a newcrates/shipper-core/crate and leavesshipperas a thin re-export façade so the rest of the workspace keeps compiling unchanged.Why this reopens #95
#135 merged a different shape:
shippercarried lib + bin, withshipper-clias a 9-line compat shim. That conflated engine code and CLI code in one crate, leftshipper-clias a shim with no value, and didn't match the goal of a no-clap library surface. The three-crate split below is what was originally asked for.What moved
All moved via
git mvso history is preserved:crates/shipper/src/{engine,state,ops,plan,runtime}/→ shipper-corecrates/shipper/src/{config,encryption,git,types,webhook,property_tests,stress_tests}.rs→ shipper-corecrates/shipper/src/lib.rs→ shipper-core (edited: droppedpub mod cli;, retargeted docs atshipper_core)What stayed in
crates/shipper/:src/cli/(moves to shipper-cli in PR 2)src/bin/shipper.rs(this crate owns the installable binary)Façade
The new
crates/shipper/src/lib.rsre-exports shipper-core's public surface (auth, cargo, cargo_failure, config, encryption, engine, git, lock, plan, registry, retry, runtime, state, store, types, webhook) so everyshipper::Ximport path still resolves. PR 3 slims this to a curated list once shipper-cli takes over the CLI.Snapshots
326 insta snapshots renamed
shipper__*.snap→shipper_core__*.snapto match the new crate prefix inmodule_path!(). No content changes.CI
Architecture-guard workflow updated to watch
crates/shipper-core/src/**(where the layer dirs now live) in addition tocrates/shipper/src/**, so the upward-import check keeps enforcing instead of silently skipping.Verification
cargo check --workspace --all-targets— cleancargo clippy --workspace --all-targets --all-features -- -D warnings— cleancargo test -p shipper-core --lib— 1889 passedcargo test -p shipper— 39 passedpreflight_command_*temp-dir locking) that pass in isolation and are unrelated to this split.What this PR does not do
crates/shipper's Cargo.toml or README (PR 3)Test plan
Closes #95 (partial — full close lands with PR 4).