Repository navigation
Conversation
…s, api and config The savings_snapshots and purchase_history account predicate had three copies (internal/analytics, internal/api, internal/config), each with its own positional-arg bookkeeping. Move it into internal/accountpredicate and switch all three callers. The config copy already used the case-insensitive provider gate from issue LeanerCloud#24 while the other two use an exact gate, so the builder takes the gate as a parameter and every caller's SQL and bind order stay byte-identical; pinning tests cover each caller and the shared builder. Refs LeanerCloud#120 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ri_utilization_cache was keyed (region, lookback_days) and the singleflight key was region#lookback, so once the reshape path reads with per-account credentials, account B would be served account A's cached Cost Explorer utilization for the same region, and B's background refresh would join A's in-flight call. Migration 000150 adds cloud_account_id to the primary key (empties the cache, which has no account identity to backfill, under an exclusive lock so an old-release writer cannot fail the NOT NULL add). The store, interface and mock take a RIUtilizationCacheKey; the cache's singleflight key includes the account. getRIUtilization and getReshapeRecommendations resolve the deployment account once, after validating query params, and key the cache with it. Refs LeanerCloud#120 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
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: none Reviewer: rG4. 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. |
govulncheck in the Docker image build now fails on these two advisories, both fixed in x/net v0.62.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked: The head moved from Body-stale: the body doesn't mention the x/net bump (non-blocking). Reviewer: rG4. 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. |
Two items from the 2026-07-28 config/db review: item 5 (three copies of the dual-column account predicate) and item 6 (
ri_utilization_cachehas no account in its key).Refs #120
Item 5: one shared account predicate
Root cause. Three separate builders produced the
cloud_account_id = ANY(...) OR (provider ... AND account_id = ANY(...))predicate:accountFilterClause+sortedProviderKeysininternal/analytics/postgres_analytics.goand ininternal/api/analytics_postgres.go, andappendAccountPredicate+sortedProviderKeysininternal/config/store_postgres.go. They have already drifted. The config copy has the issue #24 provider gate,((provider = '' OR LOWER(provider) = LOWER($p)) AND ...), but the two analytics copies still use(provider = $p AND ...). That is the partial-fix pattern the issue predicted.Fix. New leaf package
internal/accountpredicate(imports onlyfmt,sort,strings) withBuild(args, uuids, externalIDsByProvider, match). The provider gate is a parameter (ProviderExact/ProviderCaseInsensitiveOrUnknown), so each caller's SQL and bind order stay byte-identical. All three callers now use thin wrappers that keep their "no filter" conventions:"TRUE"for the analytics queries, conds unchanged for config.Pinning tests.
internal/accountpredicate/accountpredicate_test.gopins the exact clause and args for both gates and checks determinism.TestAccountFilterClause_PinnedSQL(api, new) and the strengthenedTestAccountFilterClause(analytics, exact args now) pin the per-caller output.TestAppendAccountPredicate_ProviderGate(config) gains a case for providers with no IDs.Item 6: account in the RI utilization cache key
Root cause.
ri_utilization_cachewas keyed(region, lookback_days)(migration 000031), and the singleflight key ininternal/api/ri_utilization_cache.gowasregion#lookback. The payload is one AWS account's Cost Explorer result. Once the reshape path reads with per-account credentials, account B would be served account A's cached utilization for the same region within the TTL, and B's background refresh would join A's in-flight call. The issue notes this is latent: today both callers use the single ambient identity.Fix.
Migration 000150
ri_utilization_cache_account_keydoes four things:cloud_account_id TEXT NOT NULL;(cloud_account_id, region, lookback_days);LOCK TABLE ... ACCESS EXCLUSIVEfirst, so a writer still on the previous release cannot insert a row between the DELETE and the NOT NULL add and fail the migration.The down migration empties the table and restores the old key.
''means "ambient identity not registered in cloud_accounts", because a PK column cannot be NULL.config.RIUtilizationCacheKey{CloudAccountID, Region, LookbackDays}replaces the(region, lookbackDays)arguments onGetRIUtilizationCache/UpsertRIUtilizationCache(store, interface, mock).RIUtilizationCacheEntrygainsCloudAccountID.riUtilizationCache.getOrFetchtakes the key. The singleflight key (riUtilizationFlightKey) includes the account, with fields quoted so a region string cannot alias another key.getRIUtilizationandgetReshapeRecommendationsresolve the deployment's cloud account once through a newresolveReshapeAccountScopehelper. The helper returns the account and the in-scope verdict, and that account keys the cache. Query parameters are now validated before the account is resolved, so a malformed request makes no STS or account lookup.getReshapeRecommendationsused to resolve the account twice, once for the scope check (restricted sessions only) and once for the recs lookup. It now resolves once.Behavior change to review.
getRIUtilizationnow resolves the deployment account for unrestricted sessions too. Before, only restricted sessions resolved it. This costs one STS GetCallerIdentity plus a ListCloudAccounts per request. The read now fails closed when resolution fails, including when the running AWS account is not registered while other accounts are (resolveAWSCloudAccountIDalready refuses that case).getReshapeRecommendationsalready behaved this way.TestGetRIUtilization_AccountResolutionErrorFailsClosedpins it.Rolling deploy. During a rolling deploy, a container still on the old release fails its upserts with "no unique or exclusion constraint matching the ON CONFLICT specification". The cache logs and ignores upsert errors, so it only loses that write. Its reads by
(region, lookback_days)keep working against the single ambient identity, as today.Verification
All evidence below is from unit tests with fakes/mocks or narrow integration tests against a local Postgres container. There was no real AWS call and no deployment. Ran with
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true.New tests for item 6:
TestMigration_RIUtilizationCacheAccountKey(real Postgres) checks:''key can all hold a row for the same region and lookback;TestRIUtilizationCache_Integration_AccountsInSameRegionKeepOwnRows(real Postgres) runs 8 concurrent upserts per key across three keys that share a region and lookback. Each key reads back only its own payload, and an account with no row misses.TestGetRIUtilization_AccountsInSameRegionDoNotShareCache(handler, fake store) checks that account B is served B's rows and not A's cached rows, and that A then still hits its own cached row.TestRIUtilizationCache_AccountsInSameRegionDoNotShareRowandTestRIUtilizationCache_SingleflightKeyedByAccount(cache, fake store). In the second, B's background refresh runs its own fetch while A's is blocked in flight.TestRIUtilizationFlightKey_IncludesEveryKeyFieldandTestGetRIUtilization_AccountResolutionErrorFailsClosed.Fail-before evidence comes from mutation probes, because the old API had no account parameter and the new tests cannot compile against main:
CloudAccountID: ""TestGetRIUtilization_AccountsInSameRegionDoNotShareCache%q#%dof region, lookback)TestRIUtilizationCache_SingleflightKeyedByAccount,TestRIUtilizationFlightKey_IncludesEveryKeyFieldTestGetRIUtilization_AccountsInSameRegionDoNotShareCache(plus a pre-existing SWR test)WHERE $1::text IS NOT NULL AND region = $2 AND lookback_days = $3), real PostgresTestRIUtilizationCache_Integration_AccountsInSameRegionKeepOwnRowsEach probe was reverted, and the suite passes on the final tree.
Found while testing:
TestGetRIUtilization_LookbackValidationdepends on test order. It passes in a full-package run only because a federation test helperos.SetenvsCUDLY_SOURCE_CLOUD=gcpwithout restoring it. Before the parameter-validation reorder above, it hit the no-network guard when run alone. With the reorder it passes in isolation (go test -run 'RIUtilization|Reshape|RIExchange|ExecuteExchange|ConvertibleRI|PerAccountPerms' ./internal/api/with the variable unset: ok). The leakingos.SetenvinnewFederationHandleris not changed here.Combined-tree verification. This branch was tested together with the other queued branches on a local, never-pushed tree:
queue/platform@400d256b= origin/main7b86e386plus these heads, merged in this order:Linux, Go 1.26.9,
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true; Docker Postgres via testcontainers. The full suite ran onf7c48020, which differs from this tree only by test-only commits on fix/issue-30, fix/issue-713 and fix/issue-263 that replace placeholder account-number literals with named constants:go build ./...,go vet ./...,go vet -tags integration ./...and the fullgo test -race -short -count=1 ./....--build-tags integration.gocyclo -over 10is silent on the changed non-test files.-tags integration -count=1):internal/analytics,internal/api,internal/auth,internal/config,internal/database/postgres/migrations,internal/purchase,internal/schedulerandinternal/serverall pass. Migrations 000149, 000150 and 000151 apply in order on this tree.approval-details,settings-accounts,api) pass (221 tests).accountkey); fixed with named constants. Re-run on this tree: git-secrets, using the repo'sscripts/setup-git-secrets.shpatterns in a fresh repo with an isolated HOME, found nothing in the changed files, and a planted fake key was flagged.scripts/test-git-secrets-allowlist.sh: 183 PASS, 0 FAIL..tftest.hclfiles were not run locally (CI runs them). Nothing was applied to any cloud.Out of scope
internal/analytics,internal/api) should adopt the case-insensitive / empty-provider gate the config path got in issue fix(config): account-filter provider gate drops rows with empty/wrong-case provider + NULL cloud_account_id #24. The shared builder makes that a one-word change per caller. It changes which rows the Trends and History analytics count, so it needs an owner call.cloud_account_idinri_utilization_cachehas no FK tocloud_accounts, because''is a valid key. A deleted account's rows are never read again but are not removed.handler_ri_exchange.go,handler_ri_utilization_scope_test.go), fix(config): validate ri_exchange_mode on PUT /api/config #1243, fix(api): four handler bugs found while documenting the API (#1102) #1141 (config/interfaces.go,mocks/stores.go), fix(db): contract the canceled rename: normalize, narrow the status CHECKs, stop using cancelled_by #1253 and feat(plans): settle a failed-with-submission row so a managed plan can be deleted #1241 (config/store_postgres.go,config/types.go).Maintainer: please mirror the issue's triage labels onto this PR:
impact/internal,effort/l,type/chore,triaged,priority/p3,severity/low,urgency/eventually.🤖 Generated with Claude Code