From cd9c8cac557114c8999c5b258620c8a5e08628de Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 14 Oct 2026 00:21:08 +0200 Subject: [PATCH] docs(cli): correct break-even and region filter notes; make the resize guard an invariant Co-Authored-By: Claude Sonnet 5.5 --- cmd/multi_service_engine_versions.go | 10 +++++----- docs/cli/filtering.md | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/multi_service_engine_versions.go b/cmd/multi_service_engine_versions.go index 7c5105884..a27b80a72 100644 --- a/cmd/multi_service_engine_versions.go +++ b/cmd/multi_service_engine_versions.go @@ -513,8 +513,10 @@ func adjustRecommendationForExcludedVersions(rec common.Recommendation, instance // resizeAfterExclusion sets rec to newCount instances with its money fields // scaled to match. A count of zero is the caller's drop signal and carries no -// money. A resize that common.ResizeCount refuses is also returned as zero -// rather than as a count with unscaled money. +// money. The caller derives newCount as max(0, rec.Count-excluded) with +// excluded > 0, so for newCount >= 1 it is always within [1, rec.Count] and +// common.ResizeCount cannot refuse it; a refusal means that invariant broke, +// which must not turn into a count with unscaled money. func resizeAfterExclusion(rec common.Recommendation, newCount int) common.Recommendation { if newCount < 1 { rec.Count = 0 @@ -522,9 +524,7 @@ func resizeAfterExclusion(rec common.Recommendation, newCount int) common.Recomm } resized, err := common.ResizeCount(rec, newCount) if err != nil { - log.Printf("❌ Dropping recommendation after extended-support exclusion: %v", err) - rec.Count = 0 - return rec + panic(fmt.Sprintf("extended-support resize invariant broken: %v", err)) } return resized } diff --git a/docs/cli/filtering.md b/docs/cli/filtering.md index 7ada1e3f9..469cd6e67 100644 --- a/docs/cli/filtering.md +++ b/docs/cli/filtering.md @@ -39,7 +39,7 @@ cudly --services rds --exclude-accounts sandbox --exclude-regions Exclude recommendations for these AWS regions (comma-separated) ``` -When `--regions` is omitted, cudly takes the regions from the Cost Explorer recommendations themselves (not from EC2 `DescribeRegions`), so recommendations in regions that only a linked account has opted into are included. Savings Plans are account-level and are always queried once via `us-east-1`; `--regions` is ignored for them, and `--include-regions` / `--exclude-regions` are what narrow Savings Plan results. `--include-regions` and `--exclude-regions` are applied afterwards as a filter on the fetched recommendations (matching each recommendation's region), so they reduce the results but do not change which regions are queried. If both `--regions` and `--include-regions` are set, the queried regions come from `--regions` and the results are then narrowed by the include/exclude lists. +When `--regions` is omitted, cudly takes the regions from the Cost Explorer recommendations themselves (not from EC2 `DescribeRegions`), so recommendations in regions that only a linked account has opted into are included. Savings Plans are account-level and are always queried once via `us-east-1`; `--regions` is ignored for them, and `--include-regions` / `--exclude-regions` narrow only EC2 Instance Savings Plans, which are tied to a region; Compute, SageMaker and Database plans are region-agnostic and skip the region filter. `--include-regions` and `--exclude-regions` are applied afterwards as a filter on the fetched recommendations (matching each recommendation's region), so they reduce the results but do not change which regions are queried. If both `--regions` and `--include-regions` are set, the queried regions come from `--regions` and the results are then narrowed by the include/exclude lists. ```bash # Only US and EU recommendations @@ -171,7 +171,7 @@ gross monthly savings = EstimatedSavings + upfront / term months break-even months = upfront / gross monthly savings ``` -`EstimatedSavings` is already net of the amortized upfront, so the term amortization is added back before dividing. A provider-supplied break-even wins over the formula. No-upfront recommendations (upfront 0) have a break-even of 0 and always pass. A recommendation with an upfront payment whose break-even cannot be computed (negative or non-finite money, or a term other than 1 or 3 years) is dropped, with the reason in the drop summary, because the limit cannot be enforced on it. If net savings are not positive the break-even is infinite and the recommendation is dropped. +`EstimatedSavings` is already net of the amortized upfront, so the term amortization is added back before dividing. A provider-supplied break-even wins over the formula. No-upfront recommendations (upfront 0) have a break-even of 0 and always pass. A recommendation with an upfront payment whose break-even cannot be computed (negative or non-finite money, or a term other than 1 or 3 years) is dropped, with the reason in the drop summary, because the limit cannot be enforced on it. If gross monthly savings are not positive (net `EstimatedSavings` at or below `-upfront / term months`) the break-even is infinite and the recommendation is dropped. Net savings of 0 give a break-even equal to the term. **Refused on `--input-csv` runs.** A recommendations CSV carries no break-even column, so this filter has nothing to read. Combining the two flags fails at startup rather than running with the threshold silently unenforced; drop the flag, or filter the CSV before passing it in. Teaching the CSV format to carry the column is tracked in [#1819](https://github.com/LeanerCloud/CUDly/issues/1819).