Repository navigation
fix(scheduler,api): fail closed when suppressions or grace periods can't be read - #1548
Conversation
…n't be read - scheduler: ListRecommendations, GetRecommendationByID and ScanRecommendations now return a ListActiveSuppressions error instead of logging it and serving every rec at its un-suppressed count. Suppressions are the duplicate-purchase guard, so the over-show default let a DB blip show a just-bought commitment as buyable again. applySuppressions returns no recs on error. Account overrides keep their over-show behavior. - api: gracePeriodConfig returns the GetGlobalConfig error, and executePurchase and the retry successor fail before persisting instead of writing suppressions with the default 7-day grace period (audit A01-016). - runtime: correct the IsLambda doc. Lambda container images do set AWS_LAMBDA_RUNTIME_API, and the flag only answers the freeze question. Refs LeanerCloud#111 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 58 minutes. View limit details
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 |
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:
Head moved: the brief named Body-stale: the body doesn't mention the x/net bump (non-blocking; it fixes the "Build Docker Image" govulncheck failure that every PR currently hits). 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. |
|
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: rH2. 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
Three items from #111, re-verified against
mainat7b86e386before changing anything.1. Suppression-read failure no longer over-shows (item 5, MEDIUM)
Root cause.
ListRecommendationsandGetRecommendationByID(internal/scheduler/scheduler.go) andloadSuppressionIndex(used byScanRecommendations,internal/scheduler/scheduler_scan.go) logged aListActiveSuppressionserror and continued with the un-suppressed list. Suppressions are the duplicate-purchase guard. On any DB blip, every just-bought commitment came back at its full pre-purchaseCountwith no "recently purchased" badge, so it looked buyable again.Fix. All three now return the error (fail closed), as
ListRecommendationsStrictalready did.applySuppressionsalso returns no recs on error, so a future caller that drops the error still shows nothing rather than the un-suppressed list.ScanRecommendationsfails before it reads any chunk or callsfn. HTTP callers already turn the error into a 500. The inventory coverage view (fetchCoverageOnDemandRecs) already treats a failed read as "no uncovered gap", which errs toward showing less.Unchanged on purpose. Account-override read failures still over-show. Overrides are a display filter, not a spend guard, and the issue item covers only suppressions. The comments that cited
applySuppressionsas the over-show precedent are updated:scheduler_overrides.go,config/recommendation_overrides.go,handler_dashboard.go, theListRecommendationsStrictdoc and theScanRecommendationsdoc.ScanRecommendations' chunk loop moved into ascanChunkshelper to stay under the gocyclo limit of 10. That move changes no behavior.2. Grace-period config read failure fails the purchase (A01-016, LOW)
Root cause.
Handler.gracePeriodConfig(internal/api/handler_purchases.go) swallowed theGetGlobalConfigerror and returned nil.BuildSuppressionsthen used the 7-day default. During a DB blip this wrote suppression rows for a provider the operator had switched off (grace 0), or with the wrong expiry for the others.Fix.
gracePeriodConfignow returns(cfg, error). Both call sites,executePurchaseandpersistRetryExecution, return that error before anything is persisted, asinternal/purchase(AttachResolvedRecommendations) already does.BuildSuppressions' own nil-config fallback is unchanged, since no caller passes nil any more (see "Out of scope").3.
IsLambdadoc wording (item 8, wording only)The
internal/runtime/runtime.godoc saidAWS_LAMBDA_RUNTIME_APIis "absent on container images". Lambda container images do set it. The doc now says it is absent on non-Lambda deployments (local dev, Fargate, Cloud Run, Container Apps). It also says the flag only answers whether the process is frozen between invocations, and must not be used for single-instance or shared-state questions.Refs #111
Verification
All evidence is unit/mock/fixture evidence: in-memory fakes and testify mocks, no database. Environment:
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true, Go 1.26.9.Fail-before: the new and changed tests were run on the unfixed code at
7b86e386:Pass-after: the same commands on this branch pass all of the above. The api tests also assert that
SavePurchaseExecution,LinkRetryExecutionAtomicandCreateSuppressionTxare never called, and that the error is a server error, not a 4xx.Other checks on this branch:
go build ./...,go vet ./...andgo vet -tags integration ./...: clean.go test -count=1, one package at a time:./internal/scheduler/,./internal/api/,./internal/config/,./internal/server/and./internal/mocks/allok../internal/runtime/has no test files.golangci-lint run --allow-parallel-runners(v2.10.1) on./internal/scheduler/... ./internal/api/... ./internal/runtime/... ./internal/config/...: 0 issues.gocyclo -over 10 -ignore "_test\.go"on the changed non-test files: clean.No integration test was added. The change is error propagation after an existing store read, with no SQL, schema or write-path change and no new concurrency. Real Postgres would only reproduce the failed read that the fakes inject. No migration.
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.Behavior change to note
While
purchase_suppressionscannot be read,GET /api/recommendations(list, paged and detail) and the dashboard now answer 500 instead of an un-suppressed list. This is the "cannot buy" failure direction the issue asks for. The Recommendations page already shows its "Failed to load recommendations" state for this.Out of scope (left for later)
.tfvarsescaping, theIsLambdasingle-instance consumer atinternal/server/app.goand a separate distribution discriminator, and allinternal/emailitems (9-12, and A06-005/012/014). Another agent has the email items.config.BuildSuppressionsreject a nil config, which the A01-016 note also suggests. Every caller now passes a non-nil config or fails first, so the nil fallback inBuildSuppressionsis unreachable from these paths. It was left as is to keep this change minimal.Frontend follow-up for the UI owner
None required. Optional: the Recommendations page could show a more specific message when the failure is a suppression read ("purchase history temporarily unavailable"). That would need a distinguishable error code from the API, which this PR does not add.
Maintainer: please mirror the issue's triage labels onto this PR:
priority/p3,severity/low,urgency/eventually,impact/many,effort/l,type/bug,triaged.🤖 Generated with Claude Code