Skip to content

fix(api): refuse a ramp shape change on a plan with completed steps - #689

Merged
cristim merged 3 commits into
mainfrom
fix/386-reject-ramp-shape-change
Oct 9, 2026
Merged

cristim merged 3 commits into
mainfrom
fix/386-reject-ramp-shape-change

Conversation

@cristim

@cristim cristim commented Oct 9, 2026

Copy link
Copy Markdown
Member

What

PUT /api/plans/{id} rebuilds the ramp schedule from the request. When the requested shape (type, percent per step, interval, step count) differed from the stored one, the plan restarted at step 0 and forgot the percentage already bought. A PUT that omitted ramp_schedule did the same, because it defaults to immediate.

Now updatePlan returns 409 when the stored plan has CurrentStep > 0 and the requested shape differs. The message says to resend the current ramp_schedule to keep it, or create a new plan. Same-shape edits (rename, coverage, ...) and shape changes at step 0 behave as before. The 409 is documented on PUT /api/plans/{id} in openapi.yaml.

Owner decision (fleet lead): reject rather than carry progress. Carrying the completed percentage into the new schedule (ceil(done/newPct) steps complete) remains an option if the owner wants it later; it needs an "effective target" definition that does not exist yet.

mergeUpdatedPlan is extracted from updatePlan only to stay under the gocyclo limit.

Scope of #386

Refs #386, which listed four follow-ups. Only item 1 was still open:

  • 2 (updatePlan race): fixed by 19cfb35 (conditional write, 409 on conflict)
  • 3 (run-now error mapping, ExecutedAt stamp, real status): fixed by ad84d14 and 8cb5c78
  • 4 (revoke-any account scope): fixed by 8761425

The issue stays open until this merges.

Tests

TestUpdatePlan_RampShapeChange drives PUT through the router: step 3 weekly-25% to monthly-10% (409, nothing stored), omitted ramp_schedule (409), different custom percent (409), same shape keeps step 3 (200), shape change at step 0 (200, new schedule saved). With the handler change reverted the step-3 rows fail (200 instead of 409). go test -short ./internal/api passes. Fixture-based (mock store), not a live database.

Removed TestHandler_updatePlan_ChangingRampScheduleStartsAtStepZero, which asserted the old restart behavior.

Local independent review (two passes): no actionable findings after the wording and test-coverage fixes.

🤖 Generated with Claude Code

updatePlan rebuilds the ramp schedule from the request. When the shape
(type, percent, interval, step count) differed from the stored one, the
plan restarted at step 0 and forgot the percentage already bought; a PUT
that omitted ramp_schedule did the same by defaulting to immediate.

Return 409 when the stored plan has CurrentStep > 0 and the requested
shape differs. The message tells the operator to resend the current
ramp_schedule or create a new plan. Same-shape edits and shape changes at
step 0 are unchanged. Documented the 409 on PUT /api/plans/{id}. The request-to-plan merge moves
into mergeUpdatedPlan to keep updatePlan under the cyclomatic limit.

Replaces TestHandler_updatePlan_ChangingRampScheduleStartsAtStepZero,
which asserted the restart behavior.

Refs #386
@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 4 billable files and costs up to $1.00.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 10 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 82 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: LeanerCloud/cloud-commitments-platform/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 63c88404-45d7-46da-90bf-812a614f0daa

📥 Commits

Reviewing files that changed from the base of the PR and between 30d6c84 and 613e907.


📒 Files selected for processing (4)
  • internal/api/handler_plans.go
  • internal/api/handler_plans_ramp_shape_test.go
  • internal/api/handler_plans_test.go
  • internal/api/openapi.yaml


  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

Two mutants survived the review of the ramp shape check: moving the
boundary from CurrentStep > 0 to > 1, and dropping PercentPerStep from
the shape comparison. Add a CurrentStep=1 row and a row whose stored and
requested schedules differ only in percent (custom 30% vs 33%, both 3
steps), so each mutant now fails.

Refs #386
@cristim
cristim merged commit 2a4fe44 into main Oct 9, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/many Affects most users priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant