Repository navigation
fix(purchase): stamp the purchased EC2 offering class on history - #509
Merged
Merged
Conversation
savePurchaseHistory wrote a literal "convertible" into
purchase_history.offering_class for every AWS EC2 purchase, but the EC2
client buys the class from global_config.offering_class, which can be
"standard". The marketplace-sell guard reads this column instead of
asking AWS, so every Standard RI was recorded as unsellable. The guard
also matched only the legacy "ec2" slug, so canonical "compute" recs got
no class at all.
Resolve the configured class once into an ec2types.OfferingClassType
using the same mapping as the pinned EC2 client ("" means convertible),
stamp that value for both slugs, and refuse the run with
errUnknownOfferingClass before any purchase when the value is unknown.
Closes #112
Contributor
|
Warning Review limit reached
This review includes 4 billable files and costs up to $1.00.
Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing. Or wait 13 minutes for your next included review. View limit detailsLimit details: You’ve used all 2 included reviews currently available. Your 50 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour. Review configuration: ⚙️ Run configuration
📒 Files selected for processing (4)
Comment |
The class is now stamped from the configured offering class, not a hard-coded convertible. Comment only.
cristim
pushed a commit
that referenced
this pull request
Oct 20, 2026
…ws (#1105) Before #509, savePurchaseHistory stamped every AWS 'ec2' purchase_history row 'convertible' regardless of global_config.offering_class, so Standard RIs bought while the setting was 'standard' are recorded as convertible and the marketplace-sell guard refuses them. global_config keeps no history, so only AWS can tell those rows apart. Migration 000133 implements option (a) from the issue: it sets offering_class to NULL on AWS 'ec2'/'compute' rows stamped convertible, so the marketplace-list handler's populateOfferingClass re-fetches the class from AWS and re-stamps the row. 'standard' stamps, other services and other providers are untouched; a 'standard' stamp committed while the migration waits on the row lock survives (READ COMMITTED re-check). The down migration is a no-op. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
savePurchaseHistorystamped a literal"convertible"intopurchase_history.offering_classfor every AWS EC2 purchase. The EC2 client in the pinnedcloud-commitments-go/providers/aws(services/ec2/client.goresolveOfferingClassType) buys the class fromglobal_config.offering_class, which can be"standard". The stamp also matched only the legacy"ec2"slug, so canonical"compute"recs got no class at all.The marketplace-sell guard (
internal/api/handler_marketplace.go) reads this column instead of asking AWS, so every Standard RI bought by CUDly was recorded as unsellable.How
processPurchaseRecommendationsresolves the configured class once into anec2types.OfferingClassType(resolveEC2OfferingClass, same mapping as the pinned client:""and"convertible"give Convertible,"standard"gives Standard).errUnknownOfferingClassbefore any provider client is built. Previously the error came per EC2 rec from the provider. Config writes already reject unknown values (config.validateOfferingClass), so this only fires on a tampered or corrupted row.savePurchaseHistorystamps the resolved class for AWS recs whose service maps toServiceEC2orServiceCompute(via the existingmapServiceType).PurchaseOptions.OfferingClassvalue sent to the provider is unchanged.Recommendations carry no offering class in the pinned library (
common.ComputeDetailshas none). The class is a purchase-time setting, so this PR leaves the purchase request alone and only fixes the recorded value.Verification
GOTOOLCHAIN=go1.26.6 GOWORK=off GOFLAGS='-p=2 -count=1' AWS_EC2_METADATA_DISABLED=trueinternal/purchase/money_path_regression_test.godriveprocessPurchaseRecommendationswithGlobalConfig.OfferingClassset, capture the provider'sPurchaseOptionsand the saved history row.origin/main'sexecution.go:standardcases.go build ./...,go vet ./...,go vet -tags integration ./internal/...: clean.go test ./internal/purchase/ ./internal/api/ ./internal/server/: 3276 passed.golangci-lint run ./internal/purchase/...: no issues.All evidence is mock-based (fixture provider and store); no real purchase was made. No Postgres integration test: the store write path for
offering_classis unchanged and already covered by the migration 000087 round-trip test.Deliberately left
"convertible"for Standard RIs are not backfilled. The marketplace handler only re-fetches from AWS when the column is empty, so those rows stay wrong until a backfill or a re-fetch is done. This needs an owner decision."convertible"in the RI exchange handlers (internal/api/handler_ri_exchange.go,internal/server/handler_ri_exchange.go) is correct there, because exchanges only apply to Convertible RIs.Closes #112