Repository navigation
fix(purchase): preserve purchase-detail identity during stored recommendation repricing #334
Description
Summary
Purchase repricing, scheduler IDs and stored recommendation identity omit purchase-specific detail fields. A term/payment change can therefore resolve to a recommendation with a different purchase configuration, such as EC2 tenancy, while preserving the visible account/service/region/instance-type tuple.
Current behavior and evidence
Source inspected at main 5405fd1e90dffdb701a46d4b93868b947e1fbf05 while reviewing LeanerCloud/cloud-commitments-cli#2071. internal/api/purchase_pricing.go:96 keys stored prices by provider, account, service, region, resource type, engine, term and payment. It omits detail fields; priceFromStored then copies stored details into the request.
AWS term/payment combinations are collected independently (providers/aws/recommendations/client.go:310). EC2 details carry platform, tenancy and scope from each Cost Explorer response (providers/aws/recommendations/parser_services.go:159). Rows with different terms and tenancy can coexist because term/payment are in the stored unique key. The EC2 offering query consumes tenancy (providers/aws/services/ec2/client.go:400).
An independent Astra in-memory frontend probe on both base and LeanerCloud/cloud-commitments-cli#2071 candidate used a 3-year/default-tenancy row and a 1-year/dedicated-tenancy row sharing the other cell fields. The candidate directly selected the dedicated sibling. The base retained default tenancy in its submitted details, but the source-traced backend lookup resolves that changed term to the stored dedicated row and copies those details. This broader backend identity gap therefore predates LeanerCloud/cloud-commitments-cli#2071.
The frontend guard being planned in LeanerCloud/cloud-commitments-cli#2071 narrows modal alternatives, but cannot make a shared backend identity contract complete for other callers. No live database, cloud purchase, production occurrence, or backend end-to-end execution is claimed by this evidence.
Steps to reproduce safely
- In backend fixtures, store two EC2 recommendations with the same account/service/region/instance type/engine: 3-year/default tenancy and 1-year/dedicated tenancy, with distinct corresponding prices.
- Submit a request derived from the default-tenancy row with term changed to one year while retaining its original purchase details.
- Exercise
priceAndEnforcePurchaseConstraintsthrough the real request-validation path with cloud execution stubbed. - Assert that the request must not silently acquire dedicated tenancy. Repeat for platform/scope and RDS AZ configuration after verifying their typed purchase contracts.
Expected behavior and proposed fix
Define a purchase-complete stable identity shared by collection/storage and repricing, and reject ambiguous or mismatched configurations. Inspect scheduler ID generation, recommendation storage uniqueness, internal/api/purchase_pricing.go, and provider offering filters together before deciding migration scope.
Do not compare all details wholesale: Savings Plans hourly_commitment and offering_id legitimately vary with priced alternatives, and enrichment fields are not necessarily purchase discriminators. Add tests rejecting different stable purchase configurations while accepting legitimate term/payment variants and preserving capacity scaling, account isolation and fail-closed pricing.
References
- fix(frontend): re-price purchase modal rows and stop submitting skipped fan-out buckets cloud-commitments-cli#2071 frontend fix and CodeRabbit finding: fix(frontend): re-price purchase modal rows and stop submitting skipped fan-out buckets cloud-commitments-cli#2071 (comment)
- Independent adjudication and narrowed frontend response: fix(frontend): re-price purchase modal rows and stop submitting skipped fan-out buckets cloud-commitments-cli#2071 (comment)
- fix(api): derive the purchase spend cap from the stored recommendation cloud-commitments-cli#2073 / fix(api): purchase cap is checked against client-supplied costs, not the stored price cloud-commitments-cli#1905 introduced stored-price repricing; this is a separate identity-completeness concern.
- LeanerCloud/cloud-commitments-cli#1922 concerns account omission in scheduler IDs, not the omitted purchase-detail discriminators.
Severity: high, because a mismatched commitment configuration can reach the purchase path. Priority P1, this sprint; currently demonstrated with fixtures/source tracing and a limited configuration-dependent audience, not a confirmed production incident. Estimated effort M pending identity/storage migration analysis.
Activity
cristim commentedon Sep 28, 2026
Fixed the concrete, reproducible bug in PR #406:
priceRecommendationsFromStorenow refuses a repriced purchase (term/payment changed) whose resolved stored row carries a different EC2 tenancy/platform/scope or RDS AZ config than the row the request originally pointed at, comparing only server-trusted stored data (never client-supplied Details, preserving the #1905/A01-001 invariant).Explicitly narrowing this issue's scope per its own text ("before deciding migration scope"): #406 fixes the repricing boundary only. It does not extend recommendation storage uniqueness or scheduler ID generation to include these purchase-critical discriminators. Filed #408 to track that broader storage/scheduler identity migration separately, since it needs its own design pass (new migration, ID format change, backward-compat handling for in-flight executions).
fix(purchase): close fail-open gap in purchase-detail identity check