Skip to content

decrating: physical removal of shipper-events and shipper-state#73

Merged
EffortlessSteven merged 1 commit into
mainfrom
feature/decrating-physical-events-state
Apr 15, 2026
Merged

decrating: physical removal of shipper-events and shipper-state#73
EffortlessSteven merged 1 commit into
mainfrom
feature/decrating-physical-events-state

Conversation

@EffortlessSteven

Copy link
Copy Markdown
Member

Summary

Completes Phase 2 decrating by physically moving the shipper-events (2821
LOC) and shipper-state (2725 LOC) source into shipper/src/state/events/
and shipper/src/state/execution_state/ and deleting both standalone crates.

PR #60 landed the shim absorption (re-exports from crate::state::events and
crate::state::execution_state pointing at the standalone crates) because
shipper-store (PR #57) and shipper-engine-parallel (PR #64) still had
trait-signature deps on those crates. Both blockers landed via merge train
round 2, unblocking this physical removal.

Module structure

crate::state::events/

  • mod.rs — production (EventLog, EVENTS_FILE, events_path)
  • tests.rs — unit + insta snapshot tests
  • proptests.rs — property-based tests
  • snapshots/ — 31 snapshots renamed shipper__state__events__tests__*.snap

crate::state::execution_state/

  • mod.rs — production (atomic write, encrypted I/O, schema migration)
  • tests.rs — unit + snapshot tests with two nested proptest modules
    preserved (proptests, proptests_extended)
  • snapshots/ — 16 snapshots renamed shipper__state__execution_state__tests__*.snap

Integration test moved to crates/shipper/tests/state_integration.rs (was
crates/shipper-state/tests/state_integration.rs); its 3 snapshots moved to
crates/shipper/tests/snapshots/.

Import rewrites inside shipper

  • shipper_eventscrate::state::events
  • shipper_statecrate::state::execution_state
  • shipper_environment::collect_environment_fingerprint
    crate::runtime::environment::collect_environment_fingerprint
    (execution_state no longer needs the external dep — shipper-environment
    was already absorbed into crate::runtime::environment in PR decrating: absorb shipper-environment into shipper::runtime::environment #65)

Fuzz target

fuzz/fuzz_targets/event_log_roundtrip.rs repointed to
shipper::state::events::EventLog; shipper-events removed from
fuzz/Cargo.toml.

Docs

  • Module-local CLAUDE.md files updated to remove the "transitional shim"
    note and document the now-physically-absorbed state
  • docs/architecture.md: removed dangling standalone-crate edges from the
    dependency graph; renamed the shipper-state/shipper-events entries in
    the state-layer table to point at the absorbed module paths

Validation (all green)

  • cargo check --workspace
  • cargo test -p shipper — 1640 lib tests + 26 integration tests pass
  • cargo test -p shipper-cli — all pass
  • cargo build -p shipper-cli
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo fmt --all -- --check

Per docs/decrating-plan.md §6 Phase 2 follow-up + §6.A R-PR-1..R-PR-5.

Test plan

  • Workspace cargo check passes
  • cargo test -p shipper state::events — 34 tests pass
  • cargo test -p shipper state::execution_state — 89 tests pass
  • cargo test -p shipper (full) — 1640 lib tests pass
  • cargo test -p shipper-cli — all pass
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo fmt --all -- --check

PR #60 landed shim absorption (re-exports from crate::state::events and
crate::state::execution_state pointing at standalone crates) because
shipper-store (PR #57) and shipper-engine-parallel (PR #64) had trait-
signature deps on them. Both blockers landed via merge train round 2,
unblocking this physical removal.

Move 2821 LOC (events) + 2725 LOC (state) from standalone crates into
crates/shipper/src/state/events/ and crates/shipper/src/state/execution_state/
as absorbed modules.

Events split into three files:
 - mod.rs (production: EventLog, EVENTS_FILE, events_path)
 - tests.rs (unit + insta snapshot tests)
 - proptests.rs (property-based tests)

Execution-state split into two files:
 - mod.rs (production: atomic write, migration, encrypted I/O)
 - tests.rs (unit + snapshot tests; nested proptests + proptests_extended
   submodules preserved)

Snapshots relocated and renamed for the new module path
(shipper__state__events__tests__* and
shipper__state__execution_state__tests__*).  Integration test moved to
crates/shipper/tests/state_integration.rs (was
crates/shipper-state/tests/state_integration.rs) with imports rewritten to
use shipper::state::execution_state::*.

shipper-events fuzz target (fuzz/fuzz_targets/event_log_roundtrip.rs)
repointed to shipper::state::events::EventLog; its shipper-events dep
removed from fuzz/Cargo.toml.

Internal shipper imports rewritten:
 - shipper_events → crate::state::events
 - shipper_state → crate::state::execution_state
 - shipper_environment::collect_environment_fingerprint →
   crate::runtime::environment::collect_environment_fingerprint
   (also already absorbed; execution_state no longer needs the external dep)

Delete crates/shipper-events and crates/shipper-state. Remove from workspace
members and shipper's deps. CLAUDE.md files updated to reflect the now-
physically-absorbed state. docs/architecture.md updated to remove dangling
standalone-crate edges.

Per docs/decrating-plan.md §6 Phase 2 follow-up + §6.A R-PR-1..R-PR-5.

Validation: cargo check --workspace, cargo test -p shipper (1640 lib +
integration tests), cargo test -p shipper-cli, cargo clippy --workspace
--all-targets --all-features -- -D warnings, cargo fmt --all -- --check —
all green.
@coderabbitai

coderabbitai Bot commented Apr 15, 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 18 minutes and 24 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 18 minutes and 24 seconds.

⌛ 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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5d7ec169-dcd7-4cdf-b886-47c54039888e

📥 Commits

Reviewing files that changed from the base of the PR and between 6e39a8c and 60d700a.

⛔ Files ignored due to path filters (51)
  • Cargo.lock is excluded by !**/*.lock
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__event_log_package_lifecycle.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__execution_finished_complete_failure_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__execution_finished_partial_failure_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__execution_finished_success_yaml.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__execution_started_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__full_publish_lifecycle_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__index_readiness_check_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__index_readiness_complete_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__index_readiness_started_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__multiple_events_jsonl.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__package_attempted_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__package_failed_ambiguous_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__package_failed_multiline_message_yaml.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__package_failed_permanent_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__package_failed_retryable_yaml.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__package_output_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__package_published_yaml.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__package_skipped_yaml.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__package_started_json.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__package_started_yaml.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__plan_created_yaml.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__preflight_complete_yaml.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__preflight_new_crate_detected_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__preflight_ownership_check_yaml.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__preflight_started_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__preflight_workspace_verify_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__readiness_complete_yaml.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__readiness_lifecycle_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__readiness_poll_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__readiness_started_yaml.snap is excluded by !**/*.snap
  • crates/shipper/src/state/events/snapshots/shipper__state__events__tests__readiness_timeout_debug.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__error_msg_corrupted_receipt_json.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__error_msg_corrupted_state_json.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__error_msg_empty_state_file.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__error_msg_receipt_version_invalid_format.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__error_msg_receipt_version_too_old.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__error_msg_truncated_state_json.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__receipt_all_failed.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__receipt_json_format.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__receipt_with_evidence.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__receipt_with_git_context.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__state_all_lifecycle_variants.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__state_empty_packages.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__state_json_format.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__state_transition_pending_to_failed.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__state_transition_pending_to_published.snap is excluded by !**/*.snap
  • crates/shipper/src/state/execution_state/snapshots/shipper__state__execution_state__tests__state_transition_pending_to_skipped.snap is excluded by !**/*.snap
  • crates/shipper/tests/snapshots/state_integration__receipt_all_published.snap is excluded by !**/*.snap
  • crates/shipper/tests/snapshots/state_integration__receipt_mixed_outcomes.snap is excluded by !**/*.snap
  • crates/shipper/tests/snapshots/state_integration__state_retry_cycle.snap is excluded by !**/*.snap
📒 Files selected for processing (36)
  • Cargo.toml
  • crates/shipper-cli/tests/bdd_publish.rs
  • crates/shipper-events/CLAUDE.md
  • crates/shipper-events/Cargo.toml
  • crates/shipper-events/README.md
  • crates/shipper-events/src/lib.rs
  • crates/shipper-state/CLAUDE.md
  • crates/shipper-state/Cargo.toml
  • crates/shipper-state/README.md
  • crates/shipper-state/src/lib.rs
  • crates/shipper/Cargo.toml
  • crates/shipper/src/engine/mod.rs
  • crates/shipper/src/engine/parallel/CLAUDE.md
  • crates/shipper/src/engine/parallel/mod.rs
  • crates/shipper/src/engine/parallel/publish.rs
  • crates/shipper/src/engine/parallel/tests.rs
  • crates/shipper/src/plan/mod.rs
  • crates/shipper/src/runtime/execution/CLAUDE.md
  • crates/shipper/src/runtime/execution/mod.rs
  • crates/shipper/src/runtime/mod.rs
  • crates/shipper/src/state/events/CLAUDE.md
  • crates/shipper/src/state/events/mod.rs
  • crates/shipper/src/state/events/proptests.rs
  • crates/shipper/src/state/events/tests.rs
  • crates/shipper/src/state/execution_state/CLAUDE.md
  • crates/shipper/src/state/execution_state/mod.rs
  • crates/shipper/src/state/execution_state/tests.rs
  • crates/shipper/src/state/store/snapshot_tests.rs
  • crates/shipper/tests/cross_crate_integration.rs
  • crates/shipper/tests/execution_bdd.rs
  • crates/shipper/tests/facade_integration.rs
  • crates/shipper/tests/pipeline_integration.rs
  • crates/shipper/tests/state_integration.rs
  • docs/architecture.md
  • fuzz/Cargo.toml
  • fuzz/fuzz_targets/event_log_roundtrip.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/decrating-physical-events-state

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 EffortlessSteven merged commit 783f6b9 into main Apr 15, 2026
16 of 28 checks passed

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request absorbs the shipper-events and shipper-state microcrates into the main library, consolidating event logging and state persistence logic within the crate::state module. Feedback identifies a bug in EventLog::write_to_file where using append mode with the full in-memory history leads to data duplication. Further improvements are suggested to enhance the durability of atomic writes by propagating sync_all errors and to refine receipt migration logic to avoid misleading audit trails caused by using current environment fingerprints for legacy records.

Comment on lines +96 to +100
.create(true)
.append(true)
.open(path)
.with_context(|| format!("failed to open events file {}", path.display()))?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The combination of append(true) and iterating over the entire self.events vector (lines 104-107) will cause duplication in the log file if write_to_file is called multiple times on the same instance, or if the log was previously populated from the same file via read_from_file.

Since EventLog maintains the full history in memory, it is safer to overwrite the file to ensure the on-disk state exactly matches the in-memory state. If you intend to maintain a persistent log across multiple runs, you should load the existing log first (which read_from_file does), and then overwrite the file with the updated history.

Suggested change
.create(true)
.append(true)
.open(path)
.with_context(|| format!("failed to open events file {}", path.display()))?;
let file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)
.with_context(|| format!("failed to open events file {}", path.display()))?;

.with_context(|| format!("failed to create tmp file {}", tmp.display()))?;
f.write_all(&data)
.with_context(|| format!("failed to write tmp file {}", tmp.display()))?;
f.sync_all().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.

medium

Ignoring the result of sync_all() with .ok() undermines the durability of the atomic write. If the OS fails to flush the data to physical storage (e.g., due to an I/O error or disk full), the subsequent rename might replace a valid state file with a corrupted or empty one. It is better to propagate the error to ensure the caller knows the persistence failed.

Suggested change
f.sync_all().ok();
f.sync_all().with_context(|| format!("failed to sync tmp file {}", tmp.display()))?;

Comment on lines +259 to +264
// Add environment: default EnvironmentFingerprint if not present
if receipt.get("environment").is_none() {
let environment = collect_environment_fingerprint();
receipt["environment"] = serde_json::to_value(environment)
.context("failed to serialize environment fingerprint")?;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Filling the environment field with the current environment fingerprint during migration of a v1 receipt to v2 may be misleading for audit purposes. A receipt is an audit record of a past publish operation; attributing the current tool versions and OS info to a past event reduces the reliability of the audit trail.

Consider using a placeholder indicating the information was missing in the original v1 record, or documenting that this field reflects the environment at the time of migration rather than the time of publication.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant