Repository navigation
Conversation
StrictCommitment ran the applied-scope attribution check on every VM reservation, so a single cancelled, expired, failed, split or merged Shared (or scope-less) reservation made GetExistingCommitments fail and the duplicate guard block purchases for the subscription indefinitely. Gate the applied-scope check, like the live-field completeness check, on the converted commitment state being non-terminal. The date-only expiry-today check still runs on every row, since a Succeeded reservation expiring today converts to Expired but may still be live. Closes LeanerCloud#315 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cristim
left a comment
There was a problem hiding this comment.
Independent review verdict: CLEAN at d57bfa1
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. Every terminal state the conversion can produce is excluded from coverage. The only possibly-live reservation that converts to a terminal state (expiring today) is still caught by the expiry check, which runs on every row.
Non-blocking notes:
- Split/Merged parents (Retired) skip the scope check on the assumption that Azure lists their children separately and the children are scope-checked. This matches #317's gate, but it is an API assumption that could not be checked offline.
- One mutation probe was equivalent to the original, since Active is never terminal. That is not a test gap.
What was attacked and how (local macOS; unit tests with SDK HTTP fixtures through the public GetExistingCommitments and the real recfilter.DuplicateChecker; no live Azure):
- Terminal set vs conversion:
inventory.go:283-308maps provisioning states to Expired, Canceled, Failed (Failed/BillingFailed) and Retired (Split/Merged).isTerminalCommitmentState(strict.go:55-62) lists exactly those four.- Pending states (Creating, PendingBilling, ...) map to PaymentPending, and nil or unknown states map to
"". Both still get the scope and live-field checks. - Succeeded with expiry not after now maps to Expired.
validateExpiryLifecycleruns first on every row (strict.go:36), so a same-UTC-date expiry still fails closed.
- Pending states (Creating, PendingBilling, ...) map to PaymentPending, and nil or unknown states map to
- recfilter:
isRecentActiveCommitment(pkg/recfilter/dedupe.go:99-106) returns false for Retired, PendingReturn, Expired, Canceled and Failed before any account or key use. So terminal rows attributed to the queried subscription never count as coverage, and can't cause a false "already covered" decision. - Callers:
StrictCommitmenthas one non-test caller (compute/client.go:282). The split helpers have no other users. - Ordering: expiry check, then terminal return, then scope, then live fields.
- Tests: with
GOTOOLCHAIN=go1.26.9and AWS config set to/dev/null:go test -race -count=1 ./...inproviders/azurepasses, with no SKIP seen.go test -race ./recfilter/...inpkgpasses.go vetis clean.
- Fail-before: with
strict.goreverted to main, the three new tests fail (TerminalSharedVMDoesNotFail,TerminalSharedMixedWithLive,SharedExpiryTodayStillFails). - Mutation probes: each of these is killed:
- dropping Retired from the terminal set;
- dropping Expired from the terminal set;
- moving the expiry check under the gate;
- disabling the live-field check.
Gate:
- Labels: mirrored from #315.
- CodeRabbit: skipped because the PR is a draft. A thorough review plus exact-revision local verification covers the SHA.
- CI: the fork runs need maintainer approval: 38896046545 (pre-commit) and 38896046576 (CI - Build & Test) are
action_required. Merge waits on them going green and on the draft being marked ready.
…l fails closed Document the Retired scope-check exemption's API-shape assumption and add a test that the exemption cannot hide a live child's unattributable coverage. Addresses review note on LeanerCloud#439. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Both non-blocking notes are addressed at b7cedd8, one commit on top of the reviewed d57bfa1. No rebase.
Checks for 🤖 Generated with Claude Code |
|
Delta review: CLEAN at Delta
Evidence (macOS; SDK HTTP fixtures; no Azure call):
Earlier findings re-checked: the Split/Merged API-shape assumption is unchanged and is now documented in code. CI is green on Needed before merge. @IAmJSD, please rebase onto |
|
Brought up to date with |
|
The head moved to
CI is re-running on |
|
Merge driver status: blocked on the author. The branch now conflicts with |
Closes #315
Root cause
reservations.StrictCommitment(providers/azure/services/internal/reservations/strict.go, the shared reader that #328 moved compute'svalidateVMReservationInventoryinto) ranvalidateReservationScopeon every VM reservation before the terminal-state exemption. #317 (Refs #190) only gated the live-field completeness check on non-terminal state, so on current main (740dfbd) a single cancelled/expired/failed/split/merged reservation withappliedScopeTypeShared (or missing) still madecompute.GetExistingCommitmentsreturn an error, and the duplicate guard then blocked purchases for the subscription indefinitely.Fix
validateReservationScopeis split into two checks:validateExpiryLifecycle(date-only expiry falls on today's UTC date): still runs on every row. A Succeeded reservation expiring today converts toExpired(midnight UTC is not after now), but it may still be live, so exempting terminal rows from this check would hide possibly-live coverage.validateAppliedScope(Shared / missing / unknown scope type): now runs only when the convertedcommitment.Stateis non-terminal, the same gate fix(azure/compute): fail closed on incomplete live VM reservations #317 uses forvalidateLiveReservationFields.Terminal rows are still returned in the inventory (attributed to the queried subscription, as before #274's checks).
pkg/recfilterignores Canceled/Expired/Failed/Retired, so they never count as coverage. Live Shared/missing-scope rows still fail closed, unchanged.Verification (unit tests with SDK HTTP fixtures through public
GetExistingCommitmentsand the realrecfilter.DuplicateChecker; not live Azure)All test runs use
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null.New tests in
providers/azure/services/compute/inventory_scope_sdk_test.go:TestGetExistingCommitmentsSDKTerminalSharedVMDoesNotFail: Shared VM reservations in Cancelled, Expired, Failed, Split, Merged, Succeeded-with-past-expiry, plus Cancelled with null scope type. Expects no error, the terminal state, and the guard passing the recommendation through.TestGetExistingCommitmentsSDKTerminalSharedMixedWithLive: cancelled Shared + live Single still filters the recommendation; cancelled Shared + live Shared (Succeeded, PendingBilling) still errors with "cannot attribute".TestGetExistingCommitmentsSDKSharedExpiryTodayStillFails: Succeeded Shared reservation expiring today still errors with "date-only expiryDate".Fail-before (main 740dfbd, test file only):
(
SharedExpiryTodayerrored before too, with "cannot attribute" rather than "date-only expiryDate", since the scope check ran first.)Pass-after: all three tests and subtests pass.
Mutation probes, each reverted:
TestGetExistingCommitmentsSDKExpiryTodayFailsClosedand...SharedExpiryTodayStillFailsfail...TerminalSharedVMDoesNotFailand...TerminalSharedMixedWithLivefail...SharedVMFailsClosed,...ScopeBoundary(missing/unknown),...MixedWithLivelive-Shared subtests andTestStrictCommitment_ErrorTextUsesLabelsfailCommands run (macOS):
Real-cloud gap: not exercised against a live Azure subscription with a terminal Shared reservation; the fixtures follow the armreservations JSON shape the existing #317 tests use.
Out of scope
StrictCommitmentyet, so this change only affects compute today.Maintainer: please mirror the issue's triage labels onto this PR:
priority/p2,severity/medium,urgency/this-sprint,impact/few,effort/xs,type/bug,triaged.🤖 Generated with Claude Code
Summary by CodeRabbit