Repository navigation
fix(onboarding): flag aws-cn accounts and refuse their Savings Plans purchases - #1248
Conversation
…he aws-cn Savings Plans refusal Pins providers/aws to the cloud-commitments-go#566 merge commit, which makes Savings Plans offering lookups, validation and purchases fail with savingsplans.ErrUnsupportedPartition before any AWS call when a region they see is cn-* (aws-cn bills in CNY and the purchase path is USD-only). MVS moves pkg to ff6225d, the version that commit requires. Refs LeanerCloud#1194 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…unts aws-cn accounts were onboarded with no hint that CUDly cannot buy Savings Plans for them. Account responses (create, update, get, list and the self-account) now carry a derived, non-persisted "notices" list; an AWS account whose role ARN is in aws-cn (or a self-account when CUDly itself runs in aws-cn) gets an "unsupported_partition" notice. Onboarding is still allowed so existing aws-cn accounts are not stranded. The ARN partition parsing moves to config.AWSPartitionFromARN so the account model and the STS host-identity path share it. Refs LeanerCloud#1194 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Savings Plans rec for an account whose role ARN is in aws-cn now fails with an error wrapping savingsplans.ErrUnsupportedPartition before a provider is built, on both the plan fan-out and the single-account (direct-execute) paths. Other recs in the same execution still run, so a mixed run ends partially_completed as before. The library's region-based guard stays as the backstop for accounts with no role ARN. Closes LeanerCloud#1194 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 14 billable files and costs up to $3.50. Or wait 59 minutes for your next included review. View limit details
Comment |
git-secrets' AWS account-ID detector flagged the inline sourceIdentity literal in the partition notice test, which failed the pre-commit hook and the allowlist script's scan of all tracked files. Split the literal one field per line so the placeholder sits on an `AccountID: "..."` line that .gitallowed already permits in test files. Same value, same assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
CI fix pushed. New head: Root cause. git-secrets' AWS account-ID detector ( Fix. Commit Checks run locally on the new head:
|
Refreshes the branch on current main. The red CI on the previous head came from the main breakage that LeanerCloud#1418 fixed. No files conflicted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…alias proof Main's LeanerCloud#1305 added a test that calls processPurchaseRecommendations with the old five-argument signature. This branch adds the awsPartition argument, so after the merge the purchase test package no longer compiled. The account in that test is Azure, so it passes "" like the branch's other non-AWS call sites. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged current main into the branch, which picks up #1418's fix for the main breakage behind the earlier red CI. No files conflicted. New head: f28e89c. The merge did break the build in one place, fixed in a follow-up commit. Main's #1305 added Checks:
|
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale:
Reviewer: rB21. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale:
Reviewer: rB24. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
|
Local verification at |
|
Merged by IAmJSD's agent at IAmJSD's request (IAmJSD is acting owner while the maintainers are away), under the bar IAmJSD set: 2 approving independent review(s) on |
Summary
The issue offers "warn or block". This PR does the conservative version it suggests as the safe default:
unsupported_partitionnotice.savingsplans.ErrUnsupportedPartition, before any provider is built or AWS is called.Root cause
go.modpinnedcloud-commitments-go/providers/awsataeb8a6f(2026-10-19). That predates cloud-commitments-go#566, so the library had no aws-cn refusal.awsRoleARNRegexacceptsarn:aws-cn:role ARNs, and the account API never said Savings Plans are unsupported there.cn-*. A Savings Plans rec can carry no region, so the library alone cannot reliably catch these accounts. The account's role ARN can.Fix (3 commits, each under ~400 lines)
chore(deps): bumpsproviders/awsto92722e6(the chore(deps): remove the compression override once serve depends on compression >= 1.8.2 #566 merge commit). MVS movespkgfrom541e05etoff6225d. This is the minimal bump that includes chore(deps): remove the compression override once serve depends on compression >= 1.8.2 #566. It deliberately stops before cloud-commitments-go#587/docs(api): reconcile RI-exchange approve and reject specs with observed behavior #603/fix(plans): a plan with no matching recommendations schedules zero-count purchases instead of failing #609, which rename or remove API.fix(accounts):config.AWSPartitionFromARN(the existingparseArnPartitionnow delegates to it),CloudAccount.AWSPartition()(from the role ARN, AWS accounts only) andconfig.AWSPartitionNotices.CloudAccountgets a derived, non-persistednoticesfield (omitempty, no DB or migration change).fix(purchase):rejectUnsupportedPartitionruns per rec inside theprocessPurchaseRecommendationsfan-out, beforeexecuteSinglePurchasebuilds the provider.common.IsSavingsPlan(mapServiceType(...))) on an aws-cn account. Other recs (e.g. RIs) are unaffected, so a mixed run still endspartially_completedper fix(settings): a lingering inline range error stays visible after Reset settings or a reload because programmatic value sets do not re-run the validator #642.executeForAccount(plan fan-out) and fromresolveSingleAccountProvider(direct-execute and single-account). That function now returns a smallpurchaseTarget{externalID, awsPartition}in place of the bare ExternalID.errorand the execution'serror:savings plans purchase refused for this AWS China (aws-cn) account: savings plans are not supported in the aws-cn partition: the purchase path is USD-only.Closes #1194
Verification
All evidence below is unit, mock or fixture evidence. Nothing calls AWS: tests ran with
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/nullbecause this machine's default AWS profile is invalid.Fail-before / pass-after. I temporarily disabled the guard and made
annotateAccountNoticesa no-op, then restored both:TestRejectUnsupportedPartition,TestExecuteForAccount_AWSChinaSavingsPlanRefusedBeforeAnyAWSCall,TestExecutePurchase_SingleAccount_AWSChinaSavingsPlanRefusedTestCreateAccount_AWSChinaRoleARN_ReturnsUnsupportedPartitionNotice,TestUpdateAccount_...,TestGetAccount_...,TestListAccounts_PartitionNoticesTestCreateAccount_CommercialRoleARN_HasNoNotice,TestParseArnPartition, and the commercial, GovCloud and unknown-partition cases.What the tests check:
CreateAndValidateProvideris never called for the aws-cn Savings Plans rec. The provider factory mock records calls and fails them.GetServiceClientis never called for the Savings Plans type).TestPinnedSavingsPlansClientRefusesAWSCNbuilds a real pinnedsavingsplans.Clientforcn-north-1. It uses an HTTP client that counts requests and fails them all, so there is no network access. The test assertsErrUnsupportedPartitionand zero requests.undefined: savingsplans.ErrUnsupportedPartition.CI-equivalent checks:
go build ./...andgo vet ./internal/api/ ./internal/purchase/ ./internal/config/go test <pkg> -count=1for every package in the module, one at a time: all ok, includinginternal/mocks.--allow-parallel-runnerson./internal/api/... ./internal/purchase/... ./internal/config/...(the integration build tag comes from.golangci.yml): 0 issues.gocyclo -over 10 -ignore "_test\.go"on the changed files: silent.scripts/gosec-hook.shon the changed packages: rc=0.go mod tidy: no further diff.git diff --check: clean.scripts/test-git-secrets-allowlist.sh: 137 PASS, 0 FAIL.Real-cloud gap. This has not been run against a real aws-cn account or a CUDly deployment hosted in aws-cn.
Out of scope / notes
access_keysaccounts, or a self-account on ambient credentials whose host partition is unknown, the platform cannot tell the partition without calling AWS. For those accounts:cn-*client, rec or plan-details region).purchaseErrors []string), soerrors.Iscan't see the sentinel atexecuteFailureError. Mapping it would mean carrying error values through the aggregation, which is a broader change.providers/awsandpkgcommit between the old and new pins, e.g. cloud-commitments-go#529 (skip non-USD recs at ingestion), fix(db): record the cancel actor on UI-initiated cancels #533, fix(ladder): stop netting elapsed scheduled tranches from the gap #535 (RI account scope, default unchanged) and fix(api): invalid or expired approval tokens and a missing revoke execution answer 500 instead of 4xx #564. The full platform unit suite passes on them.Frontend follow-up for the UI owner
GET/POST/PUT /api/accounts[...]andPOST /api/accounts/selfnow returnnotices: [{code, message}]when relevant (omitted otherwise). Suggested UI:unsupported_partitionmeans Savings Plans purchases are refused for that account.Labels
The issue currently has no triage labels (
gh issue view 1194 --json labelsreturns[]), so there is nothing to mirror. Could a maintainer triage #1194 and mirror its labels onto this PR?🤖 Generated with Claude Code