Repository navigation
feat(common): add OrderIDFromToken, which fails loud on a malformed idempotency token - #487
Conversation
|
@coderabbitai review |
|
Warning Review limit reached
This review includes 2 billable files and costs up to $0.50.
Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing. Or wait 59 minutes for your next included review. View limit details
Comment |
|
Independent review verdict: CLEAN at 90f9c1eReviewer: fresh Claude agent (Sonnet, high effort), not the author, cold brief. Coordinator: claude-cc-reviewer. Blocking findings: none. How the order ID is extracted:
Behaviour by input:
Note (non-blocking): characters after position 32 are not checked. For example, Caller: none yet. The body names part 2 ( What was attacked and how:
Gate: p3, no closing issue. Merge once CI is green, in its turn in the go merge order. (claude-cc-reviewer) |
…dempotency token ReservationOrderID silently returns the caller's non-idempotent fallback whenever a non-empty token cannot be formatted as a GUID (shorter than 32 chars or non-hex in the first 32). A re-drive of such a purchase then uses a fresh order ID and creates a second reservation. OrderIDFromToken returns an error wrapping the new ErrInvalidIdempotencyToken for that case, with the token masked via MaskToken; an empty token still returns the fallback. ReservationOrderID is kept unchanged, with a doc note to prefer OrderIDFromToken, so providers pinned to an older pkg keep compiling (no Deprecated: marker, which would fail SA1019 on the azure callers under go.work). PR-B switches providers/azure and removes ReservationOrderID. Refs #104 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90f9c1e to
00a0422
Compare
Move providers/azure from pkg 4566498 to 7383c3a so the savingsplans purchase path can call common.OrderIDFromToken (#487). Notable pkg changes pulled in (all additive): - feat(common): OrderIDFromToken and ErrInvalidIdempotencyToken (#487) - feat(common): EC2 platform, tenancy and scope dimensions on Commitment - fix(recfilter): match EC2 commitments on those dimensions when deduplicating - feat(insurance): decode plan line-item identity and read complete pages - docs(ladder): document provider commitment precision at PurchaseLayer Refs #104 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the purchase PUT (#495) * build(azure): bump the pkg pin past OrderIDFromToken Move providers/azure from pkg 4566498 to 7383c3a so the savingsplans purchase path can call common.OrderIDFromToken (#487). Notable pkg changes pulled in (all additive): - feat(common): OrderIDFromToken and ErrInvalidIdempotencyToken (#487) - feat(common): EC2 platform, tenancy and scope dimensions on Commitment - fix(recfilter): match EC2 commitments on those dimensions when deduplicating - feat(insurance): decode plan line-item identity and read complete pages - docs(ladder): document provider commitment precision at PurchaseLayer Refs #104 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(azure/savingsplans)!: refuse a malformed idempotency token before the purchase PUT PurchaseCommitment silently turned a non-empty but malformed idempotency token into a timestamp alias name, so a re-drive would PUT a second alias and buy a second Savings Plan. Derive the alias name with common.OrderIDFromToken right after the currency is resolved, before the body and the alias client are built, and return the error without any PUT. An empty token still falls back to the timestamp name (CLI path). Remove the now caller-less common.ReservationOrderID and its test. BREAKING CHANGE: common.ReservationOrderID was removed in favor of common.OrderIDFromToken, which returns an error wrapping common.ErrInvalidIdempotencyToken for a malformed non-empty token. Refs #104 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(azure/savingsplans): tighten the malformed-token test and fix the PurchaseCommitment doc Name OrderIDFromToken in the PurchaseCommitment doc and document the malformed-token error. Reword the comments to claim only what the test proves, and give the mock a poller so the fallback mutant fails on an assertion instead of a nil-pointer panic. Refs #104 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Part 1 of 2 for #104 item A09-020.
ReservationOrderID(token, fallback)silently returns the caller's non-idempotent fallback when a non-empty idempotency token cannot be formatted as a GUID (shorter than 32 chars, or non-hex in the first 32). A re-drive of such an Azure Savings Plan purchase then PUTs a fresh order alias and can create a second plan.This PR adds, in
pkg/common/tokens.go:ErrInvalidIdempotencyToken(exported sentinel).OrderIDFromToken(token, fallback string) (string, error): empty token returns the fallback (CLI path, unchanged); a valid token returns the deterministic GUID; a non-empty invalid token returns an error wrapping the sentinel, with the token masked viaMaskToken.ReservationOrderIDis unchanged apart from a doc note to preferOrderIDFromToken.Why additive:
make buildand lint usego.work(localpkg), while the "Verify standalone provider modules" step (ci.yml,GOWORK: 'off') buildsproviders/azureagainst its pinned olderpkg. An in-place signature change breaks one of the two. ADeprecated:marker is avoided for the same reason (staticcheck SA1019 on the azure callers undergo.work).Part 2 (after this merges): bump the
providers/azurepkg pin, switchsavingsplans/client.gotoOrderIDFromToken(validated before the purchase body and alias client are built), and deleteReservationOrderIDand its test.Token producers checked: the platform and mcp derive tokens with
DeriveIdempotencyToken(64-char lowercase SHA-256 hex); the CLI sets none. No producer sends dashed UUIDs, so the strict check rejects no real purchase.Refs #104
Tests
TestOrderIDFromTokentable:abc, 31 hex, 32 non-hex, a non-hex char inside the first 32 of a 64-char token, a dashed UUID. Each assertserrors.Is(err, ErrInvalidIdempotencyToken), that the error containsMaskToken(token)and does not contain the raw token.Mutation probes (scratch copies): returning the fallback on an invalid token, dropping the
%wwrap, printing the raw token, and leaking a 20-char prefix each fail the table.Verification
make build,make vet(all five modules,go.work),(cd pkg && GOWORK=off go test -race -short ./...),(cd providers/azure && GOWORK=off go build ./...), pinned golangci-lint v2.10.1 onpkgand onproviders/azureundergo.work(0 issues each), gofmt, and the commit/pre-push hooks pass. Unit-level evidence only.Local review: the plan had two adversarial passes by a non-author reviewer before code, and the diff had two more by the same reviewer (pass 1: masking assertion too weak, fixed; pass 2: commit message wording, fixed).
🤖 Generated with Claude Code