feat(remediate): --mark-compromised + shipper fix-forward (#98 PR 3)#134
Conversation
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
Warning Rate limit exceeded
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 36 minutes and 3 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (5)
📒 Files selected for processing (6)
✨ Finishing Touches🧪 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 |
Close the remediation staged rollout: yank (PR 1) + plan-yank (PR 2) + **fix-forward planning (this PR)** give operators the three building blocks for handling a compromised release without improvising. ## What's new ### \`shipper yank --mark-compromised\` New opt-in flag on the existing yank primitive. When set, after a successful \`cargo yank\`, shipper also amends the matching \`PackageReceipt\` in \`<state_dir>/receipt.json\` with: - \`compromised_at = Utc::now()\` - \`compromised_by = <--reason>\` These are the fields added (additively) in PR 2 (#129). The amendment lets downstream commands find compromised packages without scanning events.jsonl. Behaviour is tolerant: - No receipt → warn + proceed (yank still succeeds) - No matching package in receipt → warn + proceed - Load/write errors → warn + proceed (yank already happened; a receipt-write failure doesn't retroactively unyank) ### \`shipper fix-forward --from-receipt <path>\` New planning command. Reads a receipt, finds packages flagged \`compromised_at = Some(_)\`, and prints an ordered supersession plan: - Topological order (dependencies first, dependents last) — opposite of plan-yank, because fix-forward *publishes* replacements while plan-yank *removes* reachability. - Suggested successor versions use a placeholder format (\`<old>-next\`) so the operator picks the real bump (patch / minor / major). - Instructional preamble walks through the full remediation loop: bump Cargo.toml → commit → \`shipper publish\` → optional \`shipper plan-yank --compromised-only\` for containment. Like plan-yank, fix-forward is **planning only**. It does not edit Cargo.toml or invoke publish — those are operator steps. ## Tests 4 new tests in \`engine::fix_forward::tests\`: - only compromised+Published packages produce steps (Failed-but- compromised packages are silently dropped — they were never shipped) - topological order preserved from receipt.packages - empty plan (no compromised packages) renders with guidance toward \`--mark-compromised\` - text render enumerates steps with reason and points toward the publish + plan-yank follow-ups Plus CLI snapshots for \`yank --help\` (gains \`--mark-compromised\`) and \`fix-forward --help\`. ## Scope — explicitly NOT in this PR - **Automated Cargo.toml bump.** Workspace-edit territory; needs its own PR with --dry-run, --apply, and git-cleanliness guards. - **Publishing successors.** \`shipper publish\` already does this; fix- forward just tells the operator when to run it. - **Chaining \`superseded_by\` back to the original receipt.** Requires post-publish receipt amendment from the successor run. Follow-on. - **\`release_compromised\` top-level state.** The receipt-level marker. For now the per-package \`compromised_at\` fields cover the planning-level need.
Stitch the Remediate pillar together with a step-by-step operator guide that walks through yank --mark-compromised → fix-forward plan → publish → optional plan-yank finalize. Tied to docs/README.md's how-to index so operators can find it without folklore. Covers: - 4-step remediation flow with concrete commands - Worked example: single-CVE in a multi-crate workspace - Explicit statement of what yank does NOT do (lockfile invalidation, version bumping, consumer notification) No code changes. This only references commands added in this branch stack (#121 yank, #129 plan-yank, #131 mark-compromised + fix-forward).
46b3a2b to
cc64e72
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 46b3a2b6b3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| fn is_compromised(p: &PackageReceipt) -> bool { | ||
| p.compromised_at.is_some() && matches!(p.state, PackageState::Published) |
There was a problem hiding this comment.
Treat already-published skipped entries as fix-forward inputs
is_compromised currently requires PackageState::Published, but receipt generation can store successfully published versions as PackageState::Skipped { reason: "already published" } during resume flows (see crates/shipper/src/engine/mod.rs:746-753). If an operator marks one of those entries compromised, fix-forward silently drops it from the plan, which can leave resumed releases with an incomplete remediation plan.
Useful? React with 👍 / 👎.
| let receipt: Receipt = serde_json::from_str(&raw) | ||
| .with_context(|| format!("failed to parse receipt at {}", path.display()))?; |
There was a problem hiding this comment.
Load fix-forward receipts with schema migration support
This path deserializes JSON straight into Receipt, bypassing the version/migration logic used by state::execution_state::load_receipt and migrate_receipt. As a result, older but still-supported receipts (e.g., v1 missing newer fields) will fail in shipper fix-forward --from-receipt ..., even though the codebase otherwise supports upgrading those receipts.
Useful? React with 👍 / 👎.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Three-column reconciliation of what's actually merged vs each issue's acceptance checklist. Single source of truth for what closes each pillar issue. **Findings:** - **#90 Recover** — honestly closable. Code side is done (#124 + #130); operator-side real rehearsal is an ops action, not a code gap. - **#97 Prove tier 2** — 85% done. Rehearsal + visibility + hard gate + plan_id binding all landed (#127 + #133). Missing: install/smoke check (cargo install against the rehearsal registry / consumer build). One narrow follow-up PR closes it. - **#98 Remediate** — 60% done. Receipt schema + plan-yank (from-receipt) + yank primitive + fix-forward planning all landed (#121 + #132 + #134). Missing: plan-yank's --starting-crate graph mode, plan execution for yank + fix-forward. Two narrow follow-ups. Also captures the two review concerns on #122 (Trusted Publishing) that were addressed in a follow-up commit to that PR. Recommended next merge order and follow-up PRs spelled out at bottom.
Three-column reconciliation of what's actually merged vs each issue's acceptance checklist. Single source of truth for what closes each pillar issue. **Findings:** - **#90 Recover** — honestly closable. Code side is done (#124 + #130); operator-side real rehearsal is an ops action, not a code gap. - **#97 Prove tier 2** — 85% done. Rehearsal + visibility + hard gate + plan_id binding all landed (#127 + #133). Missing: install/smoke check (cargo install against the rehearsal registry / consumer build). One narrow follow-up PR closes it. - **#98 Remediate** — 60% done. Receipt schema + plan-yank (from-receipt) + yank primitive + fix-forward planning all landed (#121 + #132 + #134). Missing: plan-yank's --starting-crate graph mode, plan execution for yank + fix-forward. Two narrow follow-ups. Also captures the two review concerns on #122 (Trusted Publishing) that were addressed in a follow-up commit to that PR. Recommended next merge order and follow-up PRs spelled out at bottom.
Closes the remaining plan-execution gap in #98. \`shipper plan-yank --format json\` emits a YankPlan; \`shipper yank --plan <PATH>\` walks that plan and invokes \`cargo yank\` per entry. ## What's new - \`YankEntry\` / \`YankPlan\` now derive \`Deserialize\` so plans round-trip via \`--format json\` → file → \`--plan\`. The \`filter\` field moves from \`&'static str\` to \`Cow<'static, str>\` so owned strings coming off disk don't require allocation in the construction path. - \`plan_yank::load_plan_from_path\` — thin helper mirroring \`load_receipt_from_path\`. - CLI: \`shipper yank --plan <PATH>\`. Mutually exclusive with \`--crate\` / \`--version\` / \`--reason\` (clap \`conflicts_with\`); those become optional in plan mode. - Handler: iterates entries in order, emits \`PackageYanked\` event per invocation, halts on first non-zero cargo exit (reverse-topo plan means every downstream entry would be failing to yank a dependent of a crate we just failed on — continuing is damage). ## Scope — what this PR does and doesn't **Does:** - Execute yank plans end-to-end. - Correct halt-on-failure semantics. - Events-as-truth: every invocation writes a PackageYanked event (with real exit code) to events.jsonl, including failures. **Does not:** - Resume interrupted plan runs. First yank is destructive on the registry; re-running the plan with the same reason is idempotent (cargo yank of an already-yanked version is a no-op), so the simplest fix is to just re-run the plan, and operator discipline picks up the rest. - Add a \`fix-forward\` executor. That requires Cargo.toml version bumps which are workspace-edit territory — deserves its own PR with --dry-run / --apply / git-cleanliness guards. ## Tests 3 new unit tests (8 total in \`plan_yank::tests\`): - YankPlan JSON round-trips: serialize, write, load → entries + order preserved, per-entry reason preserved. - \`load_plan_from_path\` errors cleanly on missing file. - \`load_plan_from_path\` errors cleanly on malformed JSON. Regenerated \`yank --help\` snapshot. ## Closes the rest of #98's open gap With PR #138 (starting-crate) and this one, #98's acceptance checklist is fulfilled: - [x] Receipt fields (compromised_at/compromised_by/superseded_by) — PR #132 - [x] plan-yank --from-receipt + --starting-crate + --reason — PR #132 + #138 - [x] shipper yank executes a plan — **this PR** - [ ] shipper fix-forward --plan executes a plan — follow-on (needs Cargo.toml edit tooling) - [x] Docs distinguish yank semantics — PR #134 The fix-forward executor is the last remaining #98 piece. It's cleanly deferred because it adds workspace-edit scope; this PR keeps the execution story for yank self-contained.
Closes the remaining plan-execution gap in #98. \`shipper plan-yank --format json\` emits a YankPlan; \`shipper yank --plan <PATH>\` walks that plan and invokes \`cargo yank\` per entry. - \`YankEntry\` / \`YankPlan\` now derive \`Deserialize\` so plans round-trip via \`--format json\` → file → \`--plan\`. The \`filter\` field moves from \`&'static str\` to \`Cow<'static, str>\` so owned strings coming off disk don't require allocation in the construction path. - \`plan_yank::load_plan_from_path\` — thin helper mirroring \`load_receipt_from_path\`. - CLI: \`shipper yank --plan <PATH>\`. Mutually exclusive with \`--crate\` / \`--version\` / \`--reason\` (clap \`conflicts_with\`); those become optional in plan mode. - Handler: iterates entries in order, emits \`PackageYanked\` event per invocation, halts on first non-zero cargo exit (reverse-topo plan means every downstream entry would be failing to yank a dependent of a crate we just failed on — continuing is damage). **Does:** - Execute yank plans end-to-end. - Correct halt-on-failure semantics. - Events-as-truth: every invocation writes a PackageYanked event (with real exit code) to events.jsonl, including failures. **Does not:** - Resume interrupted plan runs. First yank is destructive on the registry; re-running the plan with the same reason is idempotent (cargo yank of an already-yanked version is a no-op), so the simplest fix is to just re-run the plan, and operator discipline picks up the rest. - Add a \`fix-forward\` executor. That requires Cargo.toml version bumps which are workspace-edit territory — deserves its own PR with --dry-run / --apply / git-cleanliness guards. 3 new unit tests (8 total in \`plan_yank::tests\`): - YankPlan JSON round-trips: serialize, write, load → entries + order preserved, per-entry reason preserved. - \`load_plan_from_path\` errors cleanly on missing file. - \`load_plan_from_path\` errors cleanly on malformed JSON. Regenerated \`yank --help\` snapshot. With PR #138 (starting-crate) and this one, #98's acceptance checklist is fulfilled: - [x] Receipt fields (compromised_at/compromised_by/superseded_by) — PR #132 - [x] plan-yank --from-receipt + --starting-crate + --reason — PR #132 + #138 - [x] shipper yank executes a plan — **this PR** - [ ] shipper fix-forward --plan executes a plan — follow-on (needs Cargo.toml edit tooling) - [x] Docs distinguish yank semantics — PR #134 The fix-forward executor is the last remaining #98 piece. It's cleanly deferred because it adds workspace-edit scope; this PR keeps the execution story for yank self-contained.
…#139) Closes the remaining plan-execution gap in #98. \`shipper plan-yank --format json\` emits a YankPlan; \`shipper yank --plan <PATH>\` walks that plan and invokes \`cargo yank\` per entry. - \`YankEntry\` / \`YankPlan\` now derive \`Deserialize\` so plans round-trip via \`--format json\` → file → \`--plan\`. The \`filter\` field moves from \`&'static str\` to \`Cow<'static, str>\` so owned strings coming off disk don't require allocation in the construction path. - \`plan_yank::load_plan_from_path\` — thin helper mirroring \`load_receipt_from_path\`. - CLI: \`shipper yank --plan <PATH>\`. Mutually exclusive with \`--crate\` / \`--version\` / \`--reason\` (clap \`conflicts_with\`); those become optional in plan mode. - Handler: iterates entries in order, emits \`PackageYanked\` event per invocation, halts on first non-zero cargo exit (reverse-topo plan means every downstream entry would be failing to yank a dependent of a crate we just failed on — continuing is damage). **Does:** - Execute yank plans end-to-end. - Correct halt-on-failure semantics. - Events-as-truth: every invocation writes a PackageYanked event (with real exit code) to events.jsonl, including failures. **Does not:** - Resume interrupted plan runs. First yank is destructive on the registry; re-running the plan with the same reason is idempotent (cargo yank of an already-yanked version is a no-op), so the simplest fix is to just re-run the plan, and operator discipline picks up the rest. - Add a \`fix-forward\` executor. That requires Cargo.toml version bumps which are workspace-edit territory — deserves its own PR with --dry-run / --apply / git-cleanliness guards. 3 new unit tests (8 total in \`plan_yank::tests\`): - YankPlan JSON round-trips: serialize, write, load → entries + order preserved, per-entry reason preserved. - \`load_plan_from_path\` errors cleanly on missing file. - \`load_plan_from_path\` errors cleanly on malformed JSON. Regenerated \`yank --help\` snapshot. With PR #138 (starting-crate) and this one, #98's acceptance checklist is fulfilled: - [x] Receipt fields (compromised_at/compromised_by/superseded_by) — PR #132 - [x] plan-yank --from-receipt + --starting-crate + --reason — PR #132 + #138 - [x] shipper yank executes a plan — **this PR** - [ ] shipper fix-forward --plan executes a plan — follow-on (needs Cargo.toml edit tooling) - [x] Docs distinguish yank semantics — PR #134 The fix-forward executor is the last remaining #98 piece. It's cleanly deferred because it adds workspace-edit scope; this PR keeps the execution story for yank self-contained.
Reopened after #132 merge. Original PR #131 auto-closed when base branch was deleted.
PR 3 of the #98 Remediate staged rollout:
Completes the Remediate rollout alongside the now-merged #121 and #132.