You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(azure): RI exchange silently forces AppliedScopeType to Shared, reallocating the discount #57
An Azure RI exchange executed through POST /api/ri-exchange/azure-instances/exchange silently forces every acquired reservation to AppliedScopeType = Shared, discarding whatever scope the source reservation had. That is a silent reallocation of real money between cost centres, mentioned in neither the request nor the response.
Where
providers/azure/services/compute/exchange_operations.go defaults to Shared when tgt.AppliedScopeType is nil.
toAzureExchangeTargets (internal/api/handler_ri_exchange.go) never sets it.
AzureExchangeTargetBody has no such field, and openapi.yaml documents none - so no caller can control it.
ExchangeableReservation does not carry the source's applied scope either, so it cannot currently be preserved even in principle.
Concrete scenario
A reservation is deliberately Single-scoped to the prod subscription for chargeback. It is SKU-swapped through this endpoint and comes back Shared. Dev/test workloads now absorb the discount and prod pays on-demand. Nothing in the API surface told the caller this would happen.
Fix direction (any one, in preference order)
Carry the source's AppliedScopeType (and applied scopes) through the exchange - requires adding the field to ExchangeableReservation so the tenant listing surfaces it.
Expose applied_scope_type on AzureExchangeTargetBody and require it, so the caller states intent explicitly.
At minimum, document the non-preservation in openapi.yaml and in the handler's response, so the behaviour is at least not silent.
Found during adversarial review of PR LeanerCloud/cloud-commitments-cli#1515 (Azure RI exchange execute). Out of scope for that PR, which fixed the Regions-constraint source gap.
Releasing my claim: checked against main 740dfbd, this is already fixed here by #209 (merged 2026-10-05 with Refs #57). ExchangeableReservation now carries the source AppliedScopeType/AppliedScopes (providers/azure/services/compute/exchange.go:60-64), validateAppliedScope refuses an empty or unsupported scope (exchange.go:287), and the calculateExchange request copies the source scope onto each target (exchange_operations.go:440-470). No Shared default remains. The remaining caller work (passing the listed source with its scope, mapping the refusal to a 4xx, openapi) is tracked in LeanerCloud/cloud-commitments-platform#521. Marking needs-info for a close decision.
Activity
fix(azure): send applied scopes only for Single and type the scope re…
fix(azure): carry the source reservation's applied scope through an R…
cristim commentedon Oct 16, 2026
Claiming: working on this.
cristim commentedon Oct 16, 2026
Releasing my claim: checked against main
740dfbd, this is already fixed here by #209 (merged 2026-10-05 withRefs #57).ExchangeableReservationnow carries the sourceAppliedScopeType/AppliedScopes(providers/azure/services/compute/exchange.go:60-64),validateAppliedScoperefuses an empty or unsupported scope (exchange.go:287), and the calculateExchange request copies the source scope onto each target (exchange_operations.go:440-470). No Shared default remains. The remaining caller work (passing the listed source with its scope, mapping the refusal to a 4xx, openapi) is tracked in LeanerCloud/cloud-commitments-platform#521. Marking needs-info for a close decision.