Skip to content

fix(purchase): preserve purchase-detail identity during stored recommendation repricing #334

Description

@cristim

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

  1. 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.
  2. Submit a request derived from the default-tenancy row with term changed to one year while retaining its original purchase details.
  3. Exercise priceAndEnforcePurchaseConstraints through the real request-validation path with cloud execution stubbed.
  4. 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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions