Reviewed commit: be11bdcb5. Note: origin/main moved to 3e9660d06 during the review; re-verify against current main before changing code, since a finding may have been fixed or moved.
Where
providers/aws/recommendations/coverage.go:275-287 (fetchCoveragePaged, the loop over result.CoveragesByTime) with the record closures at :226 and :250
providers/aws/recommendations/coverage.go:186 (windowHours = lookbackDays * 24) and :359-361 (poolCoverageFromGroup computing AvgInstancesPerHour = TotalRunningHours / windowHours)
- Correct sibling implementation:
providers/aws/recommendations/sp_coverage.go:217-266 (spCoverageAccumulator.add / .summarize), which pins Granularity: DAILY and accumulates across every period before dividing
- Downstream consumers:
providers/aws/recommendations/coverage.go:409-456 (ApplyCoverageMapToRecommendations) and providers/aws/ladder/layer_states.go:100 (computeEC2CoveragePct)
What
fetchCoveragePaged walks result.CoveragesByTime, which holds one entry per Cost Explorer time bucket, and for each bucket calls record(...), which does an unconditional out[key] = cov. Whatever bucket CE happens to return last silently replaces every earlier bucket, so the map ends up holding a single bucket's running hours.
AvgInstancesPerHour is then computed as TotalRunningHours / windowHours, where windowHours is the full lookback (lookbackDays * 24). So one bucket's hours are divided by the whole window.
The Savings Plans path does this correctly. The RI path is the outlier, and it is structurally unable to control the bucketing: it sets GroupBy and therefore cannot also set Granularity, so the number of buckets CE returns is not under its control.
Failure scenario
GetRICoverageMap(ctx, 30, ["us-east-1"]) run on the 2nd of a month. CE returns two buckets: the previous month, and the 2-day partial current month. The 2-day bucket wins.
A pool genuinely running 40 instances/hr reports TotalRunningHours of roughly 1920 (40 x 48h), divided by windowHours = 720, giving AvgInstancesPerHour = 2.67 instead of 40, roughly a 15x understatement. Pct is likewise taken from a 2-day sample rather than the requested 30 days.
ApplyCoverageMapToRecommendations then rescales every recommendation's AverageInstancesUsedPerHour down by that factor (recs[i].AverageInstancesUsedPerHour *= cov.AvgInstancesPerHour / recSum), so a --target-coverage run buys roughly 15x fewer RIs than the pool actually needs. The under-buy is silent: no error, no warning, and the numbers look internally consistent. The same distortion feeds the ladder's EC2 coverage input via computeEC2CoveragePct.
The magnitude depends on where in the month the run happens, so the same account sized on the 2nd and on the 28th produces materially different purchase plans from identical usage.
Fix direction
Accumulate TotalRunningHours and hour-weighted coverage across all CoveragesByTime entries per pool key before dividing, mirroring spCoverageAccumulator.add / .summarize, instead of last-write-wins. Add a test that feeds two buckets of unequal length for the same pool key and asserts the summed hours (this fails pre-fix, where only the last bucket survives).
Related
Findings from the 2026-09-02 codebase audit
Added by an automated audit of 3c0f8ac94048a2c36fce5ccddee54e6c4849a5cd (tip of origin/main). Each item below was reported by one reviewer and independently confirmed by a second that did not write it. Full report: docs/audits/codebase-audit-2026-09-02.md.
A07-019 (medium)
The sibling this issue points at as correct has the same divisor bug. spCoverageAccumulator.add increments a.days only for buckets that carried a Coverage block (providers/aws/recommendations/sp_coverage.go:222-240), but summarize at :246-248 divides the accumulated dollars by the caller's lookbackDays*24 unconditionally (:355). Cost Explorer lags 24-48h and omits buckets with no eligible activity, so a 30-day request that returns 10 populated days reports CoveredUSDPerHour and EligibleUSDPerHour at a third of the true rate. Days is returned so a caller could detect it, but spCoverageAdapter.GetSPCoverageSummary (providers/aws/ladder/adapters.go:257) keeps only CoveragePct and no caller reads Days. CoveragePct itself is a ratio and is unaffected, and a repo-wide grep finds no production reader for the three rate fields, so nothing is displayed wrong today. Worth fixing in the same pass so the accumulator pattern this issue adopts is correct at the source. Finding A07-019.
Reviewed commit:
be11bdcb5. Note:origin/mainmoved to3e9660d06during the review; re-verify against currentmainbefore changing code, since a finding may have been fixed or moved.Where
providers/aws/recommendations/coverage.go:275-287(fetchCoveragePaged, the loop overresult.CoveragesByTime) with therecordclosures at:226and:250providers/aws/recommendations/coverage.go:186(windowHours = lookbackDays * 24) and:359-361(poolCoverageFromGroupcomputingAvgInstancesPerHour = TotalRunningHours / windowHours)providers/aws/recommendations/sp_coverage.go:217-266(spCoverageAccumulator.add/.summarize), which pinsGranularity: DAILYand accumulates across every period before dividingproviders/aws/recommendations/coverage.go:409-456(ApplyCoverageMapToRecommendations) andproviders/aws/ladder/layer_states.go:100(computeEC2CoveragePct)What
fetchCoveragePagedwalksresult.CoveragesByTime, which holds one entry per Cost Explorer time bucket, and for each bucket callsrecord(...), which does an unconditionalout[key] = cov. Whatever bucket CE happens to return last silently replaces every earlier bucket, so the map ends up holding a single bucket's running hours.AvgInstancesPerHouris then computed asTotalRunningHours / windowHours, wherewindowHoursis the full lookback (lookbackDays * 24). So one bucket's hours are divided by the whole window.The Savings Plans path does this correctly. The RI path is the outlier, and it is structurally unable to control the bucketing: it sets
GroupByand therefore cannot also setGranularity, so the number of buckets CE returns is not under its control.Failure scenario
GetRICoverageMap(ctx, 30, ["us-east-1"])run on the 2nd of a month. CE returns two buckets: the previous month, and the 2-day partial current month. The 2-day bucket wins.A pool genuinely running 40 instances/hr reports
TotalRunningHoursof roughly 1920 (40 x 48h), divided bywindowHours = 720, givingAvgInstancesPerHour = 2.67instead of 40, roughly a 15x understatement.Pctis likewise taken from a 2-day sample rather than the requested 30 days.ApplyCoverageMapToRecommendationsthen rescales every recommendation'sAverageInstancesUsedPerHourdown by that factor (recs[i].AverageInstancesUsedPerHour *= cov.AvgInstancesPerHour / recSum), so a--target-coveragerun buys roughly 15x fewer RIs than the pool actually needs. The under-buy is silent: no error, no warning, and the numbers look internally consistent. The same distortion feeds the ladder's EC2 coverage input viacomputeEC2CoveragePct.The magnitude depends on where in the month the run happens, so the same account sized on the 2nd and on the 28th produces materially different purchase plans from identical usage.
Fix direction
Accumulate
TotalRunningHoursand hour-weighted coverage across allCoveragesByTimeentries per pool key before dividing, mirroringspCoverageAccumulator.add/.summarize, instead of last-write-wins. Add a test that feeds two buckets of unequal length for the same pool key and asserts the summed hours (this fails pre-fix, where only the last bucket survives).Related
computeEC2CoveragePctblending non-EC2 pool keys into reported EC2 coverage). Verified as distinct: LeanerCloud/cloud-commitments-cli#1482 is about which pool keys are included, this is about the producer discarding all but one time bucket. Fixing either one leaves the other live.package main) is adjacent context for the--target-coveragepath.Findings from the 2026-09-02 codebase audit
Added by an automated audit of
3c0f8ac94048a2c36fce5ccddee54e6c4849a5cd(tip oforigin/main). Each item below was reported by one reviewer and independently confirmed by a second that did not write it. Full report:docs/audits/codebase-audit-2026-09-02.md.A07-019 (medium)
The sibling this issue points at as correct has the same divisor bug. spCoverageAccumulator.add increments a.days only for buckets that carried a Coverage block (providers/aws/recommendations/sp_coverage.go:222-240), but summarize at :246-248 divides the accumulated dollars by the caller's lookbackDays*24 unconditionally (:355). Cost Explorer lags 24-48h and omits buckets with no eligible activity, so a 30-day request that returns 10 populated days reports CoveredUSDPerHour and EligibleUSDPerHour at a third of the true rate. Days is returned so a caller could detect it, but spCoverageAdapter.GetSPCoverageSummary (providers/aws/ladder/adapters.go:257) keeps only CoveragePct and no caller reads Days. CoveragePct itself is a ratio and is unaffected, and a repo-wide grep finds no production reader for the three rate fields, so nothing is displayed wrong today. Worth fixing in the same pass so the accumulator pattern this issue adopts is correct at the source. Finding A07-019.