Repository navigation
refactor(ladder): fetch commitment rates per layer so one missing rate holds only its layer - #691
Conversation
…e holds only its layer GetCommitmentRates returned an empty map as soon as the EC2 Instance SP rate was missing. That is harmless with one layer, but would drop every other layer once more are added. The fetch is now a loop over an explicit layer list (layer, plan type, region): a missing rate holds only that layer, any other error fails the call naming the layer, and a rate that is not finite or not positive is rejected instead of stored. The list still holds only EC2 Instance SP. Compute SP stays absent until the owner decides; its account-wide blend over EC2, Lambda and Fargate biases sizing of a regional EC2-only gap. Part of #349 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 35 minutes. View limit details
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. |
|
APPROVED 32ff7fd by claude-qa-ui-4 Cold review. I didn't write this change. Checked against the stated contract (
Probes in a scratch copy (each must fail a test):
Runs (scoped and throttled, in |
cristim
left a comment
There was a problem hiding this comment.
APPROVE @ 32ff7fd by claude-nibblets
Cold review of 32ff7fd, the shared part of #349 (ladder sizing, money path), done in my own detached worktree. The branch is 0 behind main. The PR body was treated as a claim.
Against what correct means
- Each layer's rate is fetched independently.
fetchLayerRates(providers/aws/ladder/commitment_rates.go) copiesbaseper layer and setsPlanTypeandRegionfrom the layer.SPCommitmentRateParams(providers/aws/recommendations/sp_commitment_rate.go:24-38) holds only scalars, so the per-layer copy can't leak state between layers. ErrNoSPCommitmentRatedrops only that layer. It iscontinue, so the layer is absent and the others are still fetched.- Any other error fails loudly. It returns a nil map and an error naming the layer (
GetCommitmentRates: layer <layer>: %w), wrapping the cause, soerrors.Isstill matches. - Unusable rates are errors. NaN, ±Inf and values of zero or below are refused with a nil map, never treated as 1:1 or 0. The source already refuses NaN and
r <= 0(sp_commitment_rate.go,GetSPCommitmentRate), so this is the ladder's own check and doesn't depend on the source. - Behaviour is unchanged today. The production list has only
LayerEC2InstanceSPwith the ladder's region. A missing rate still gives an empty non-nil map, as the oldmap[...]{}return did, and a valid rate still gives{EC2InstanceSP: rate}. Compute SP stays absent, as the code comment says, pending the owner's decision.
Verification at this SHA
In providers/aws, with GOWORK=off GOMAXPROCS=4, under the shared review lock: go vet ./ladder/ is clean and go test -p 2 -count=1 ./ladder/ passes.
Mutation probes (each reverted): 9 of 9 killed.
| Mutation | Result |
|---|---|
| The old empty-map return restored on a missing rate (the claimed fail-before) | killed (TestFetchLayerRates_LayersAreIndependent/first_missing_second_present, /both_missing) |
| The unusable-rate check removed | killed (TestGetCommitmentRates_RejectsUnusableRate) |
Zero accepted (< 0) |
killed (RejectsUnusableRate/zero) |
| Inf accepted | killed (RejectsUnusableRate/inf) |
| A hard error swallowed as absent | killed (first_present_second_hard, HardErrorNamesLayer, AbsenceAndErrors) |
| A partial map returned with a hard error | killed (AbsenceAndErrors/hard, first_present_second_hard) |
| The layer name dropped from the error | killed (HardErrorNamesLayer) |
| Region not taken from the layer | killed (LayersAreIndependent, all three subtests that check the region) |
| Compute SP added to the production layer list | killed (AbsenceAndErrors/absent, ForwardsSelection/0). The unchanged-behaviour claim is pinned |
CI at this SHA: Lint for every module, pre-commit, Integration Tests, Snyk and Lint Workflows pass. Unit Tests and Security Scanning were still running when I posted this. CodeRabbit is NEUTRAL.
Not blocking
- No upper bound. A rate is commitment dollars per covered on-demand dollar, and nothing in the ladder or the source refuses a rate above 1. A Savings Plan that costs more than on-demand isn't a usable sizing input. Since this PR already treats the rate as a money-path input with a validity check,
(0, 1]would match the range #686's observed rates enforce. A follow-up would do.
Not merging.
|
APPROVED c700726 by claude-qa-ui-4 Merge-only delta from my APPROVE at 32ff7fd.
The earlier verification (5/5 probes killed, ladder tests and vet green) applies unchanged. |
cristim
left a comment
There was a problem hiding this comment.
APPROVE @ c700726 by claude-nibblets
Merge-only delta review of 32ff7fdb...c7007262, the update-branch onto main. I approved 32ff7fdb earlier. This is a comment review because GitHub does not let the PR author's account approve its own PR.
- The merge brings in only
main's #684.c7007262has two parents:32ff7fdb(the approved head) and07b7b48d(#684, "refuse purchase counts that don't fit int32 before any AWS call"), which is the currentmaintip. #684 is the only commit between them. - No conflict resolution.
git show --remerge-diff c7007262is empty, so git merged it automatically with no hand edits. - No overlap.
main's side of the merge doesn't touchproviders/aws/ladder. #684 changes theaws/servicespurchase paths andofferingprice. - The PR's own change is identical.
git diff 07b7b48d c7007262(2 files, +123/-19) is byte-identical to32ff7fdb's diff against its base, apart fromindexlines. So the code I reviewed and mutation-probed at32ff7fdb(9 of 9 probes killed) is what merges.
CI at this SHA: Lint for every module, pre-commit, Lint Workflows and Snyk pass. Unit Tests, Integration Tests and Security Scanning were still running when I posted this. CodeRabbit is NEUTRAL.
Not merging.
|
Merged at head c700726 as 790f369. Gate evidence:
|
* feat(ladder): give Compute SP its Cost Explorer commitment rate GetCommitmentRates now fetches a Compute SP rate beside the EC2 Instance SP one, through the per-layer fetch from #691: a missing rate holds only that layer, other errors fail loud, and nothing falls back to 1:1 or 0. The Compute SP rate is Cost Explorer's account-wide blend over EC2, Lambda and Fargate in all regions, so for a regional EC2-only gap it can over-buy by r_blend/r_EC2. The owner accepted that bias (option A on #349); the per-run cap and the platform's per-sweep cap bound it. Tests cover both layers present, either one missing, a hard Compute error after an EC2 success, the Compute call's parameters, and an end-to-end SDK consumer run that now allocates and builds tranches for both layers. Closes #349 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(ladder): state what actually bounds the Compute SP blend bias The comment said the per-run and per-sweep caps bound the bias, but in this library MaxHourlyCommitPerRun is optional and there is no per-sweep cap; the platform plans a required one (#70). The comment now says so. The SDK consumer test's us-east-1 Compute row now has a different ratio from the eu-west-1 row (0.75 vs 0.5), so the expected 0.625 blend proves both regions count; a regional filter would give 0.5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(ladder): pin that the requested term and payment reach Cost Explorer TestGetCommitmentRates_ForwardsSelection only requested 3yr partial-upfront, so a GetCommitmentRates that hardcoded either value still passed and a 1yr no-upfront ladder could be sized at a 3yr rate. It now also requests 1yr no-upfront and 3yr all-upfront and asserts the exact term and payment on both the EC2 Instance SP and Compute SP requests. Hardcoding the term or the payment now fails it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(ladder): point the per-sweep cap note at the platform issue "(#70)" in this repository links cloud-commitments-go#70; the per-sweep cap is planned in cloud-commitments-platform#70. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
GetCommitmentRates(providers/aws/ladder/commitment_rates.go) fetched only the EC2 Instance SP rate. OnErrNoSPCommitmentRateit returned an empty map. That was harmless with one layer, but any layer added later would be dropped whenever the EC2 rate is missing. This is the part that every option for #349 needs, whichever way the open Compute SP decision goes.GetCommitmentRatesnow fetches throughfetchLayerRates(ctx, src, layers, base), oneGetSPCommitmentRatecall per layer:ErrNoSPCommitmentRate: only that layer is absent (held), and the loop continues;Nothing defaults to 1:1 or 0.
Tests
TestFetchLayerRates_LayersAreIndependentuses two synthetic layers:first_missing_second_presentandboth_missing.commitment_rates_sdk_test.gois unchanged and passes.go build,go vet,go test ./ladder/andgolangci-lint(0 issues) pass, and so does pre-commit, all rebased on main 8344b3d.Part of #349
🤖 Generated with Claude Code