Repository navigation
fix(config): canonicalise dash-free Savings Plans spellings in suppressions - #1456
Conversation
…ssions The purchase manager's mapSavingsPlansSlug accepts both the hyphenated per-type Savings Plans slugs (savings-plans-compute) and the dash-free ones (savingsplans-compute), and the purchase API stores the service a client sends verbatim. A direct API client that bought with the dash-free spelling therefore wrote a suppression the scheduler compared literally against the hyphenated rec, so the rec it had just bought stayed on show and could be bought again. The UI only sends the hyphenated form, so this is reachable only through direct API clients. CanonicalSuppressionService now maps the four dash-free per-type spellings to the library constants. The "savingsplans" umbrella is left alone so one umbrella suppression can never hide every plan type, and non-AWS providers (Azure's "savingsplans" service) pass through. The Savings Plans slug map moves to a package var so a drift test can range over it and check each per-type spelling against the config alias table, as the legacy AWS slug test already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 5 billable files and costs up to $1.25. Or wait 58 minutes for your next included review. View limit details
Comment |
|
Pushed 32881eb (head 32881eb): a single commit that canonicalises the four dash-free per-type Savings Plans spellings in |
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale:
Reviewer: rD3. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale:
Reviewer: rE1. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
|
Local verification at |
|
Merged by IAmJSD's agent at IAmJSD's request (IAmJSD is acting owner while the maintainers are away), under the bar IAmJSD set: 2 approving independent review(s) on |
Keep both new tests in slug_alias_drift_test.go: this branch's TestMapServiceSlugAcceptsEveryCanonicalReservationSlug and main's TestSavingsPlansSpellingsMatchSuppressionAliases (LeanerCloud#1456). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes #1422
Item 1 (tie the alias tables to the library constants, plus a drift test) was done in #1425. This PR covers item 2.
Root cause
mapSavingsPlansSlugininternal/purchase/execution.goaccepts both spellings of each per-type Savings Plans slug:savings-plans-compute(thecommon.ServiceSavingsPlans*constant that recs carry) andsavingsplans-compute.validatePurchaseRecommendationstores the service a client sends as-is, soBuildSuppressionswrites the suppression under whichever spelling the client used.config.CanonicalSuppressionServiceonly knew the legacy AWS RI slugs and compared Savings Plans spellings literally. A purchase sent with the dash-free spelling therefore wrote a suppression that never matched the hyphenated rec. The rec it had just bought stayed on show and could be bought again.Reachability, checked before changing anything: the frontend sends only the hyphenated per-type slugs, and nothing in the repo emits the dash-free ones. So only a direct API client (
POST /api/purchases/executewith a hand-written body) can reach this. That matches the maintainer's finding on the issue.Fix
config.awsSavingsPlansSpellingsmaps the four dash-free per-type spellings to the library constants.CanonicalSuppressionServiceconsults it after the legacy RI table, for AWS only.savingsplansumbrella is deliberately not mapped, so one umbrella suppression can never hide every plan type, as the issue asks. Azure'ssavingsplansservice and other non-AWS providers pass through unchanged.mapSavingsPlansSlug's map moves to a package var (savingsPlansSlugs) so a drift test can range over it, the same way chore(config): tie suppression slug aliases to the library service constants #1425's test coverslegacyAWSServiceSlugs. Lookup behaviour is unchanged.Verification
TestCanonicalSuppressionService: 8 new rows. They cover the 4 dash-free spellings, idempotence on the hyphenated form, the umbrella passthrough, and Azuresavingsplans/savingsplans-computepassthrough.TestApplySuppressions_AWSLegacySlugSpellings(scheduler): 5 new rows. They cover a dash-free suppression hiding a hyphenated rec and the reverse, the database plan type, an umbrella suppression not hiding a compute rec, and a compute suppression not hiding a sagemaker rec.TestSavingsPlansSpellingsMatchSuppressionAliases(purchase): every per-type key insavingsPlansSlugsmust canonicalise to the slug it buys, and umbrella keys must pass through.config/types.gochange reverted, all three tests fail. After the change they pass.go build ./...,go vet ./...,go vet -tags integration ./...and the e2e vet pass.--build-tags integration.gocyclo -over 10is clean on the changed files.go test -race -shortpasses on config, scheduler and purchase.go test -race -tags integrationalso passes on all three (testcontainers Postgres; the first attempts timed out starting containers on a loaded host, and the reruns passed).scripts/test-git-secrets-allowlist.shpasses.🤖 Generated with Claude Code