Skip to content

fix(azure): report only savings plans billed to the subscription - #207

Open
cristim wants to merge 4 commits into
mainfrom
fix/azure-savingsplans-subscription-scope
Open

cristim wants to merge 4 commits into
mainfrom
fix/azure-savingsplans-subscription-scope

Conversation

@cristim

@cristim cristim commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Azure Savings Plan inventory now returns only plans billed to the client's subscription and returns an error when ownership cannot be resolved. Previously, unresolved rows could disappear from a successful inventory result, weakening duplicate-purchase checks.

Subscription billing scope determines ownership. Applied benefit scope does not establish who pays. Known foreign subscription rows are excluded; missing, malformed, unknown or billing-account scopes fail the entire inventory rather than returning a partial list. Owned rows require a nonblank plan ID. Purchase and validation use the same subscription billing-scope helper.

Published candidate: a1f9ad611ad32958fffa7c36ff52630bd0636006. The complete feature diff is three files, 263 changed lines. The candidate integrates main ac0f2696; the subsequent six-path delta to observed main eaa9f740 was independently checked for compatibility.

Local verification:

  • Actual pinned Azure SDK pagination, ownership and public duplicate-checker fixtures: 30 test events passed with race detection on the committed candidate.
  • Shared recommendation-filter consumer suite: 167 test events passed with race detection on the committed candidate.
  • Earlier baseline and targeted behavioral mutations exposed the intended inventory failures. Integrated package checks and five-module builds passed.
  • Explicit pre-commit coverage of the 18 feature/imported-main paths passed 13 hooks. The ordinary conflict-free merge hook skipped its checks; that skipped run is not coverage evidence.
  • Independent final source, commit and raw-evidence audits found no actionable issues. Source, workspace, module, executable and postflight bindings matched.

CI is running on the published commit. These are offline synthetic fixtures, not real Azure account or purchase acceptance. Real-account acceptance and the exact final-HEAD reviewer gate remain required; keep this PR open. Issue #40's broader purchase-orchestration acceptance remains unresolved.

Refs #40

GetExistingCommitments lists savings plans with the tenant-wide ListAll
endpoint (/providers/Microsoft.BillingBenefits/savingsPlans) and stamped
every returned plan with the client's subscription. In a tenant with
several subscriptions, each subscription reported every other
subscription's plans as its own, exposing their IDs and amounts and
double-counting committed spend.

Keep only plans whose billingScopeId equals /subscriptions/<id>
(case-insensitive), the same scope PurchaseCommitment buys under. A plan
is billed to exactly one scope even when its applied scope is Shared, so
each plan is now reported once, by the subscription that pays for it.
Plans billed elsewhere or with no billing scope are skipped and counted
in a warning log line.

Closes #40
@cristim cristim added triaged Item has been triaged priority/p1 Next up; this sprint severity/high Significant harm urgency/this-quarter Within the quarter impact/many Affects most users effort/m Days type/security Security finding labels Oct 5, 2026
@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: LeanerCloud/cloud-commitments-go/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 8458e8bb-ae1e-418a-9a0e-919b777bc4c2
📥 Commits

Reviewing files that changed from the base of the PR and between 8727842 and 67ebe09.

📒 Files selected for processing (2)
  • providers/azure/services/savingsplans/client.go
  • providers/azure/services/savingsplans/client_test.go

Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.


📝 Walkthrough

Walkthrough

The Azure Savings Plans client filters tenant-wide results to plans attributable to its subscription. It logs skipped plans, with details capped at 20 entries, and uses a shared helper to build subscription billing scopes for purchase and validation requests.

Changes

Azure Savings Plan subscription filtering

Layer / File(s) Summary
Filter results and use subscription billing scope
providers/azure/services/savingsplans/client.go, providers/azure/services/savingsplans/client_test.go
GetExistingCommitments retains plans billed to the client subscription and plans with a single-subscription applied scope matching it. It skips other identifiable plans and logs the full skipped count with up to 20 plan details. Tests cover scope matching, exclusions, case-insensitive IDs, and the logging limit. Purchase and validation requests use the shared billingScopeID() helper.

Priority: ⬆️ High

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: High

Merge Risk: ⚪ Minimal · up to 67ebe

No actionable issue is established from the supplied evidence; the change is mergeable after normal checks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed [ #40 ] GetExistingCommitments now keeps plans billed directly to the client subscription, or plans billed to another scope only when their Single applied scope names that subscription. It rejects…
Out of Scope Changes check ✅ Passed All reviewed changes support [ #40 ]. The shared billingScopeID() helper keeps purchase and validation scopes consistent with the direct-subscription ownership rule. The added tests verify the filte…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: filtering Azure savings plans to those attributable to the client subscription.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

…ling account

ListAll responses on EA and MCA tenants report billingScopeId as a
billing account (/providers/Microsoft.Billing/billingAccounts/...), not
/subscriptions/{id}; see the SavingsPlansList.json, SavingsPlansInOrderList.json
and SavingsPlanItemGet.json examples in azure-rest-api-specs
billingbenefits 2022-11-01. Matching on the billing scope alone hid every
such plan, including from the duplicate-purchase check.

A plan is now ours when its billing scope is /subscriptions/{id}, or when
it is billed to a non-subscription scope, its applied scope type is
Single and appliedScopeProperties.subscriptionId is /subscriptions/{id}
(both compared case-insensitively). A plan billed to another subscription
stays that subscription's even when applied to ours. Shared plans billed
to a billing account cannot be attributed to one subscription and are
still skipped.

The skip warning now carries the total count and, for up to 20 plans,
each plan ID with its billing and applied scope.
@cristim

cristim commented Oct 5, 2026

Copy link
Copy Markdown
Member Author

Independent source/contract review of exact head 67ebe092210a86d799b0417da7524537b1361197 found a purchase-safety blocker beyond the live-account acceptance hold:

  • providers/azure/services/savingsplans/client.go:166-171 warns about skipped plans, then returns success. Billing-account Shared plans and unresolved/malformed attribution can therefore become a successful empty or incomplete inventory.
  • pkg/recfilter/dedupe.go:44-46 already propagates enumeration errors, while :54-55 permits all recommendations when successful inventory contains no recent commitments. No new public completeness interface is necessary to fail closed here.
  • Microsoft's ListAll example returns a billing-account billingScopeId, despite the SDK property comment mentioning a subscription. Application scope does not establish the purchasing subscription: https://learn.microsoft.com/en-us/rest/api/billingbenefits/savings-plan/list-all?view=rest-billingbenefits-2022-11-01 .

Proposed minimal correction: distinguish attributable rows, definitively foreign subscription billing, and unresolved attribution. Preserve safe foreign exclusion; return an error rather than a successful partial inventory on unresolved rows. Exercise the actual Azure SDK client through DuplicateChecker, with behavioral baseline/mutation evidence and a reachable purchase consumer proving zero purchase calls on that error. This does not implement full Shared-plan attribution or prove every direct purchase caller is protected.

I am preparing the scoped plan and local verification while readiness #488 remains with its existing owner. Please flag overlapping ownership before edits to this PR branch. No source changes, tests, cloud calls or merges have been made for this review. Independent Astra replaces the unavailable exact reviewer under the user's instruction; the separate real-account acceptance standard is awaiting clarification.

Return an error and no inventory when subscription ownership cannot be
established. Preserve proven foreign subscription filtering and exercise
the real SDK pager through public inventory and duplicate checking.

Refs #40
Preserve the reviewed savings plan inventory fix while incorporating
main through ac0f269.

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

Labels

effort/m Days impact/many Affects most users priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/security Security finding urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant