feat(remediate): plan-yank + compromised-release receipt fields (#98 PR 2)#129
feat(remediate): plan-yank + compromised-release receipt fields (#98 PR 2)#129EffortlessSteven wants to merge 2 commits into
Conversation
…ds (#98 PR 2) Next step on the Remediate pillar. PR 1 (#121) landed the single-crate \`shipper yank\` primitive; this PR composes it into a reverse-topological containment plan built from a prior publish receipt. ## What's new - **Receipt schema** (additive): PackageReceipt gains three Option fields, all \`#[serde(default, skip_serializing_if = \"Option::is_none\")]\` so existing receipts read back unchanged: - \`compromised_at: Option<DateTime<Utc>>\` — when this version was marked compromised - \`compromised_by: Option<String>\` — operator-supplied reason (CVE, incident, free-form tag) - \`superseded_by: Option<String>\` — populated by \`shipper fix-forward\` (#98 PR 3) - **\`shipper::engine::plan_yank\`** module: - \`PlanYankFilter::{AllPublished, CompromisedOnly}\` selector - \`YankPlan\`, \`YankEntry\` value types - \`build_plan(receipt, filter)\` — filter receipt.packages, reverse to get dependents-first order, emit entries - \`render_text(plan)\` — produces copy-pasteable \`shipper yank ...\` command lines, one per entry, with \`--reason <REASON>\` placeholders for the operator to fill in - \`load_receipt_from_path\` — receipt loader for arbitrary paths (default is \`<state_dir>/receipt.json\`) - **\`shipper plan-yank\` CLI subcommand**: \`\`\` shipper plan-yank [--from-receipt PATH] [--compromised-only] \`\`\` Honors the global \`--format\` flag: \`text\` (default) prints the copy-pasteable yank-command list; \`json\` prints structured YankPlan for scripting. ## Scope — explicitly NOT in this PR - **Plan execution** (wrapping \`shipper yank\` to run each entry) → #98 PR 3 along with fix-forward. - **\`--mark-compromised\`** (populating the new receipt fields programmatically) → #98 PR 3. For now, operators can mark packages compromised by hand-editing receipt.json or via downstream tooling. - **release_compromised state** (higher-level "this release is a write-off") → #98 PR 3. ## Why reverse topological, specifically For workspace A → B → C (A is a leaf, B depends on A, C depends on B): - Publish order: A, B, C - **Yank order: C, B, A** — dependents first. New resolves for C will still pull B (if B is not yet yanked), but at least C itself is unresolvable. Yanking A first would be fine semantically but can mislead operators who read \`cargo search\` output. Plus: reverse-topo matches the containment mental model users bring from incident response — "pull the thing furthest from your reach first, work back toward the root." ## Tests 5 unit tests in \`engine::plan_yank::tests\` (all green): - reverses publish order for \`AllPublished\` - excludes Failed/Skipped packages (only Published gets a yank entry) - \`CompromisedOnly\` filter drops healthy packages, keeps only flagged - empty receipt → empty plan with a helpful comment - text-render ordering invariant: dependents appear before dependencies Plus a CLI \`plan-yank --help\` snapshot in \`e2e_expanded\`. ## Companion churn Mechanical: every PackageReceipt construction site (engine + state tests + integration tests) now sets the three new Option fields to None. The schema is additive; no runtime semantics changed for existing code paths.
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ 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 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 68f4091188
ℹ️ 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".
| .map(|r| format!(" # {r}")) | ||
| .unwrap_or_default(); | ||
| out.push_str(&format!( | ||
| "{:>3}. shipper yank --crate {} --version {} --reason <REASON>{reason}\n", |
There was a problem hiding this comment.
Emit executable shell lines in text yank plan
render_text prefixes every generated command with an ordinal like 1.. In sh/bash, that token is parsed as the command name, so shipper plan-yank ... | sh fails for any non-empty plan before shipper yank is invoked. This contradicts the command help that says text output can be piped through sh, and makes the default text plan unusable for its primary execution path.
Useful? React with 👍 / 👎.
| let receipt_path = from_receipt.unwrap_or_else(|| { | ||
| opts.state_dir | ||
| .join(shipper::state::execution_state::RECEIPT_FILE) | ||
| }); |
There was a problem hiding this comment.
Resolve default receipt path from workspace root
When --from-receipt is omitted, plan-yank builds the path from opts.state_dir directly, unlike other commands that anchor a relative state_dir to planned.workspace_root. If the user runs from a different CWD (for example with --manifest-path /path/to/ws/Cargo.toml), this looks for .shipper/receipt.json in the wrong directory and reports a missing/unreadable receipt even though one exists under the workspace.
Useful? React with 👍 / 👎.
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
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).
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).
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).
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).
…134) * feat(remediate): --mark-compromised + shipper fix-forward (#98 PR 3) 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. * docs: how-to for remediating a compromised release (#98) 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).
Summary
Next step on the Remediate pillar. PR 1 (#121) landed the single-crate `shipper yank` primitive; this PR composes it into a reverse-topological containment plan derived from a prior publish receipt.
What's new
Receipt schema (additive, zero migration): `PackageReceipt` gains three `Option` fields, all `#[serde(default, skip_serializing_if)]`:
`shipper::engine::plan_yank` module:
`shipper plan-yank` CLI:
```
shipper plan-yank [--from-receipt PATH] [--compromised-only]
```
Honors `--format text|json`. Default `text` output is directly
pipeable into `sh` (with some review) once you've filled in the
`` placeholders.
Example
```
$ shipper plan-yank --from-receipt .shipper/receipt.json --compromised-only
yank plan (reverse topological) — registry=crates-io, plan_id=abc, filter=compromised_only
2 entries
```
Scope — explicitly NOT in this PR
Why reverse-topological
For A → B → C (A leaf, B depends on A, C depends on B):
Dependents first so downstream consumers stop resolving against the compromised chain before the root crate itself is pulled.
Tests
Companion churn
Every `PackageReceipt` construction site (engine code + state/execution tests + integration tests) sets the three new Option fields to `None`. Additive schema, no runtime semantics change.
Test plan