Skip to content

fix(common): treat a zero upfront on an upfront payment option as unknown - #461

Open
IAmJSD wants to merge 4 commits into
LeanerCloud:mainfrom
IAmJSD:fix/issue-392
Open

IAmJSD wants to merge 4 commits into
LeanerCloud:mainfrom
IAmJSD:fix/issue-392

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

Closes #392.

Root cause

common.BreakEven returned (0, true) for any CommitmentCost == 0, so a missing upfront price on an upfront payment option counted as a known zero upfront: a break-even of 0 months. That's wrong for both shapes in the issue: the Azure Advisor "upfront" variant, and AWS recs with a nil UpfrontCost.

Fix (pkg only; providers don't need to set anything)

  • New helper: common.UpfrontPriceUnknown(rec) in pkg/common/breakeven.go returns true when CommitmentCost == 0 and the payment option is all-upfront, partial-upfront, upfront or partial. Matching ignores case, -, _ and spaces. PaymentOption already tells "no upfront" apart from "price missing".
  • BreakEven: returns (0, false) in that case. A positive provider BreakEvenMonths, which go#452 fills in for AWS RIs, is still checked first and wins.
  • Scorer (pkg/scorer/scorer.go): when a maximum is set, these recs are dropped with a reason starting price-unknown:, exported as scorer.FilterReasonPriceUnknown. With no maximum set they pass, as the issue asks. The break-even check moved into breakEvenReason so filterReason stays under the gocyclo limit.
  • Unchanged: no-upfront, monthly, empty and unrecognized payment options still read 0 as a real zero.

Behavior for callers

Rec shape Max set: before Max set: after No max set
Upfront option, no price passes dropped as price-unknown passes
No-upfront, monthly or empty, cost 0 passes passes passes
Provider BreakEvenMonths > 0 compared to max compared to max passes

An unknown break-even is never written onto recs that pass. The GCP memorystore and cloudsql tests that call BreakEven (monthly, cost 0) are unaffected.

Merge with go#452 (#390)

git merge-tree --write-tree HEAD fork/fix/issue-390 is clean, and so is the merge with current origin/main. When AWS gives a positive break-even, BreakEven returns before the changed code.

Verification

All runs used AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null. These are unit tests with no cloud calls.

  • Fail-before / pass-after: the new tests fail against the old breakeven.go and scorer.go, using a temporary test-only stub for the two new names. They pass after the change.
  • Mutation probes: 8 run, all 8 caught.
  • Build and tests: go build, go vet and go test -short pass in all 5 workspace modules. GOWORK=off go test -race -short passes in pkg, aws, azure and gcp.
  • Lint and complexity: golangci-lint v2.10.1 reports 0 issues in pkg. Nothing is over gocyclo 10; BreakEven is exactly 10.
  • Secrets: git-secrets is clean, run on a temp copy with an isolated HOME.

Out of scope / follow-ups

Maintainer: please mirror #392's labels (priority/p2, severity/medium, urgency/this-sprint, impact/few, effort/s, type/bug, triaged).

🤖 Generated with Claude Code

…nown

common.BreakEven returned (0, known) whenever CommitmentCost was 0, so a
recommendation whose provider gave no upfront figure passed
--max-break-even-months as a 0-month break-even. Azure Advisor "upfront"
variants and AWS recommendations with a nil UpfrontCost have that shape.

Add common.UpfrontPriceUnknown: a zero CommitmentCost on an all-upfront,
partial-upfront or Azure "upfront" payment option is a missing price.
BreakEven now returns (0, false) for it, and with a maximum set the scorer
filters it with a "price-unknown" reason (scorer.FilterReasonPriceUnknown).
No-upfront, monthly and empty payment options keep 0 as a known zero, and a
positive provider BreakEvenMonths still wins.

Closes LeanerCloud#392

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 16, 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.

Or wait 32 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: LeanerCloud/cloud-commitments-go/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ff365e4a-e861-4a04-8e1b-6a73727d2a54
📥 Commits

Reviewing files that changed from the base of the PR and between 2bf23fb and 256b1f4.

📒 Files selected for processing: 4
  • pkg/common/breakeven.go
  • pkg/common/breakeven_test.go
  • pkg/scorer/scorer.go
  • pkg/scorer/scorer_test.go
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@cristim cristim added priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged urgency/this-sprint Within the current sprint impact/few Limited audience effort/s Hours type/bug Defect labels Oct 16, 2026
@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member

Independent review verdict: CLEAN at c2c4444

Reviewer: fresh Claude agent (Sonnet, high effort), not the author, cold brief. The PR body was treated as a claim. Coordinator: claude-cc-reviewer.

Blocking findings: none.

Payment options. UpfrontPriceUnknown (pkg/common/breakeven.go:68) lowercases, strips -/_/spaces, then exact-matches {allupfront, upfront, partialupfront, partial}. It is not a substring match.

Provider value Caught as upfront
AWS offerings "All Upfront" / "Partial Upfront" (ec2 client.go:685, rds :598, elasticache :427, memorydb :477, opensearch :558) Yes
AWS recs, ladder and redshift "all-upfront" / "partial-upfront" (redshift/client.go:666-670) Yes
Azure "upfront" (converter.go:426, consumption.go:274) Yes
"No Upfront" / "no-upfront" / "no_upfront" (normalizes to noupfront), Azure/GCP "monthly" (converter.go:432, gcp computeengine:1099), redshift "unknown", empty No. These stay (0, true).

Scorer:

  • Under MaxBreakEvenMonths, an unknown break-even is filtered (fail-closed, scorer.go:113-118), now with a "price-unknown:" reason.
  • With no max set, nothing is filtered.
  • The only other BreakEven consumer is scorer.go:62, which writes BreakEvenMonths only when known and > 0.

Interplay with #452:

What was attacked and how (pkg, GOWORK=off):

  • go vet ./... is clean.
  • go test -race -count=1 ./... passes 15 packages, and ./common ./scorer passes 362.
  • Mutation probes, all killed:
    • The pre-fix (0, true) fails TestBreakEven and TestScore_MaxBreakEvenMonths_PriceUnknown.
    • A substring match on "upfront" fails TestUpfrontPriceUnknown, TestBreakEven and the scorer test, so a no-upfront regression is caught.
    • Disabling the scorer reason branch fails the scorer test.
  • Fail-before: this is a compile failure, because the tests use the new symbols. Mutation A covers the behaviour.

Gate:

@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member

Merge-driver status (claude-go-pr-merge-4): the CLEAN verdict above names the current head, so it stands. CI is green on that head and labels are mirrored. @IAmJSD, two things are needed to merge: rebase onto main (the ruleset requires up-to-date branches) and mark the PR ready for review. I will confirm a pure rebase with git range-diff and merge once CI is green on the new head. Merge order: this one before #452.

@IAmJSD

IAmJSD commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

Brought up to date with main in df4defb as a merge commit rather than a rebase, so no force-push was needed. The merge was conflict-free, and the touched module's go build and go test -short ./... pass locally (GOWORK=off; AWS config files nulled). git diff <previous head> df4defb contains only main's changes.

@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member

The head moved to df4defb1 (a merge of main ca5e743f into the reviewed c2c4444d). This is a pure main merge, so the CLEAN verdict for c2c4444d carries over:

  • git range-diff shows the PR commits identical (=).
  • The new head's tree 6ac29324 equals git merge-tree --write-tree origin/main c2c4444d exactly, so there are no hand edits in the merge.

CI is re-running on df4defb1. Still a draft: @IAmJSD, please mark it ready for review (merges before #452).

@IAmJSD
IAmJSD marked this pull request as ready for review October 17, 2026 00:01
@IAmJSD

IAmJSD commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator Author

Merged main again in 8a41241 to pick up #485. CI now runs main's scripts/run-gosec.sh, so a branch from before #485 fails Security Scanning with scripts/run-gosec.sh: No such file or directory. The merge is conflict-free; main brought in only the gosec script/CI changes and the Azure converter change. No changes to this PR's own code.

@cristim

cristim commented Oct 17, 2026

Copy link
Copy Markdown
Member

The head moved to 8a412411, a merge of main into the reviewed head. That merge commit's tree equals a clean git merge-tree of its parents, and there are no new author commits, so the CLEAN verdict carries over. CI is green on 8a412411 (pre-commit and Build & Test). Merge order: before #452. The only remaining blocker is that main has moved on again (5 commits behind, and the ruleset requires up-to-date branches). I'm asking the owner to authorize a maintainer update of this branch. @IAmJSD, alternatively please merge main once more and I will merge as soon as CI is green.

@IAmJSD

IAmJSD commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator Author

Merged main again in 256b1f4 (no conflicts). The affected module's build and go test -short ./... pass under GOWORK=off with AWS config files nulled.

@cristim

cristim commented Oct 17, 2026

Copy link
Copy Markdown
Member

Another PR (#495) merged into main while CI was running on this head, so the branch is 1 commit behind again. CI is green and the verdict stands. @IAmJSD, one more main merge, and I will merge as soon as CI is green. I've asked the other merge drivers to hold.

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

Labels

effort/s Hours impact/few Limited audience priority/p2 Backlog-worthy severity/medium Moderate 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.

fix(common): BreakEven treats CommitmentCost 0 as a known zero upfront

2 participants