Skip to content

fix(azure): treat reservation purchaseDate as a calendar date in the duplicate guard - #453

Merged
cristim merged 2 commits into
mainfrom
fix/277-azure-date-only-purchase
Oct 16, 2026
Merged

cristim merged 2 commits into
mainfrom
fix/277-azure-date-only-purchase

Conversation

@cristim

@cristim cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member

Summary

Part 2 of #277, the Azure side. Part 1 (#447, b69a17e) added common.Commitment.StartDateIsDay and the day-resolution check in the recent-commitment duplicate guard.

The two commits are kept separate so the pin bump can be judged on its own:

  1. build(azure) bumps the azure pkg pin from v0.0.0-20261012192523-3838efd56668 to v0.0.0-20261016163842-4566498f2a08.
  2. fix(azure) sets StartDateIsDay = true in the shared reservation inventory (providers/azure/services/internal/reservations/inventory.go), where StartDate comes from the date-only PurchaseDate.
    • armreservations v1.1.0 / API 2022-03-01 uses format: date.
    • All seven reservation clients go through this one function.
    • The Savings Plans path uses the date-time EffectiveDateTime and stays precise, and a test now pins that.

Effect: a reservation bought late yesterday now suppresses a matching duplicate recommendation. Before, it decoded to UTC midnight and looked older than the 24h window.

Wire shape: Azure reservation commitments now serialize "start_date_is_day":true.

Closes #277

Verification (macOS, Go 1.26.9)

Commands

  • (cd providers/azure && GOWORK=off go test -race -short ./...): pass.
  • make build, make vet: pass.
  • golangci-lint v2.10.1 (CI pin): 0 issues.
  • gofmt, gocyclo -over 10: clean.

SDK-decode regression (compute/purchase_date_guard_sdk_test.go)

  • Real reservation JSON goes through the SDK pager and GetExistingCommitments, then NewDuplicateChecker(24).AdjustRecommendationsForExisting, using the real clock.
  • If the UTC date changes mid-run, the test skips rather than fail.
  • Cases:
    • Filtered: today, yesterday.
    • Passed: two days ago; canceled yesterday (excluded by state); expired yesterday (excluded by state).

Fail-before: without the inventory line, purchased_yesterday fails.

Other tests

Review

  • The plan had two passes by a non-author reviewer.
  • The same reviewer did two diff passes. Pass 1 led to the Savings Plans guard.
  • No actionable findings on the final series.
  • Rebased onto 512e42f and re-tested.

All of this uses SDK-shaped fixtures. No Azure call or purchase was made.

🤖 Generated with Claude Code

@cristim cristim added priority/p2 Backlog-worthy severity/high Significant harm urgency/this-quarter Within the quarter impact/few Limited audience effort/m Days type/bug Defect triaged Item has been triaged labels Oct 16, 2026
@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 5 billable files and costs up to $1.25.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 6 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 65 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: LeanerCloud/cloud-commitments-go/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 4a47fc46-6f7f-4bb6-93d8-4f56e6b88db4
📥 Commits

Reviewing files that changed from the base of the PR and between ae6fbfe and 21b70e0.

⛔ Files ignored due to path filters: 1
  • providers/azure/go.sum is excluded by !**/*.sum
📒 Files selected for processing: 5
  • providers/azure/go.mod
  • providers/azure/services/compute/purchase_date_guard_sdk_test.go
  • providers/azure/services/internal/reservations/inventory.go
  • providers/azure/services/internal/reservations/inventory_test.go
  • providers/azure/services/savingsplans/client_test.go
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member Author

Independent review verdict: CLEAN at c2849d2

Reviewer: fresh Claude agent (Sonnet, high effort), not the author, cold brief. The PR body was treated as a claim. Coordinator: claude-cc-reviewer.

Blocking findings: none.

SDK (armreservations v1.1.0, module cache):

  • PurchaseDate is a *time.Time (models.go:591), decoded by unpopulateDateType (models_serde.go:1818) with layout "2006-01-02" (date_type.go:22,32-35). So "2026-10-16" becomes 00:00 UTC.
  • A malformed value errors (date_type.go:50-57), and the pager error propagates out of ListAppliedReservations. A parse failure therefore fails closed.

The guard:

  • go.mod bumps only the pkg pin, to 4566498, which carries StartDateIsDay handling (pkg/recfilter/dedupe.go:116-119: midnight + 24h compared against the cutoff).
  • The PR sets the flag at inventory.go:268-269.
  • So a date-only purchase is treated as the latest possible moment of that day:
Case Result
Purchased today, any time Duplicate blocked
Purchased 23:30 UTC yesterday, seen 20:00 UTC today (the bug) Blocked
Purchased 2 days ago Not blocked
Exactly at the cutoff Not blocked, since After is strict (acceptable)
Canceled or expired Excluded before the date check
Unknown or empty state Counted as owned (fails closed)

Consumers:

  • All 7 reservation clients go through CommitmentFromReservation (internal/reservations/strict.go:30): compute, database, cache, search, cosmosdb, synapse and managedredis.
  • Savings Plans (savingsplans/client.go:259) use the date-time EffectiveDateTime, so they stay precise. The test asserts the flag is false there.

Non-blocking, already present before this PR: a nil PurchaseDate leaves StartDate zero, so the row looks ancient and the guard fails open. Filed as #454.

What was attacked and how (providers/azure, GOWORK=off, no Azure calls):

  • go vet ./... is clean. go test -race -count=1 ./... gives 12 ok and 0 FAIL.
  • Fail-before: with inventory.go reverted to main, TestCommitmentFromReservation_StartDateUsesPurchaseDate and TestPurchaseDateGuardSDKDateOnlyPurchase/purchased_yesterday fail.
  • Mutation probes, both killed:
    • setting the flag to false (the same 2 tests fail);
    • shifting StartDate by -24h (3 tests fail).

Gate: labels are mirrored from #277. Merge once CI is green at this SHA.

@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member Author

CI is green on head c2849d2e: pre-commit 38937839183 and Build & Test 38937839205.

cristim and others added 2 commits October 16, 2026 20:32
Moves providers/azure from pkg 3838efd to 4566498 so the module sees
common.Commitment.StartDateIsDay and the recfilter change from b69a17e.

pkg commits in between that touch packages azure imports:
b69a17e fix(recfilter): treat a date-only commitment start as any time that d...
498b74f feat(common): preserve proven commitment currency metadata (#410)
77bc8a9 feat(common): add ExperimentalGCPPlanTerm to RecommendationParams (#402)
6fd189f fix(recfilter): coverage drop reason, EC2 platform engine filter, mon...
9551cce fix(scorer): compute break-even so --max-break-even-months filters
e9a33bb feat(pkg): add recurring monthly cost to the audit record
cdcdc7f feat(common): add RecommendationParams.SPAccountScope (step 1 of 2)

Azure build and race tests pass on the bumped pin before any code change.

Refs #277

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reservations API returns purchaseDate as a date, so a reservation
bought late yesterday decoded to 00:00 UTC yesterday and fell outside the
24h duplicate guard. Set StartDateIsDay so the guard treats the instant as
anywhere in that day. Adds an SDK-decode regression through
DuplicateChecker with the real clock, and a partial-overlap test that
checks ResizeCount scales the recommendation costs.

Refs #277

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cristim
cristim force-pushed the fix/277-azure-date-only-purchase branch from c2849d2 to 21b70e0 Compare October 16, 2026 18:32
@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member Author

Pure rebase onto main after go#446 merged. git range-diff of c2849d2 against new head 21b70e0 shows identical commits (=), so the CLEAN verdict carries. (claude-cc-reviewer)

@cristim
cristim merged commit e75ac25 into main Oct 16, 2026
15 checks passed
@cristim
cristim deleted the fix/277-azure-date-only-purchase branch October 16, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/few Limited audience priority/p2 Backlog-worthy severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(azure): handle date-only PurchaseDate in the recent-commitment guard

1 participant