Skip to content

fix(server): report the ladder plan's net existing commitment (bump cloud-commitments-go) - #1461

Draft
IAmJSD wants to merge 2 commits into
LeanerCloud:mainfrom
IAmJSD:fix/issue-243-rest
Draft

IAmJSD wants to merge 2 commits into
LeanerCloud:mainfrom
IAmJSD:fix/issue-243-rest

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator

Why

#243's last open item (finding 8, cross-repo): assembleLadderPlan added up each layer's gross ExistingUSDPerHour and wrote that total into the ladder plan, ladder_runs.existing_usd_hr and the approval email's Explain() output. Allocate, though, sizes the gap against net existing commitment (existing minus expiring). Take a layer with $8/hr OD existing, of which $4/hr expires, and a $10/hr target. The plan showed Existing $8/hr next to a $6/hr gap, so an approver could not get from target minus existing to the gap.

The go side landed in LeanerCloud/cloud-commitments-go#614: AllocateResult.NetExistingUSDPerHour. The platform's pkg pin (v0.0.0-20261020053130-ff6225d5ff04) was older than that change, so this PR bumps the pin and then reads the net figure.

The other #243 findings were fixed earlier: #1265 has the per-finding table and lists finding 8 as the only deferred item. I re-checked them on current main. So this PR closes the issue.

Closes #243

What

Commit 1: chore(deps). Moves pkg and providers/{aws,azure,gcp} to go main 80e9b8464d2a (v0.0.0-20261026081621-80e9b8464d2a) via go get <module>@<sha> and go mod tidy. It also includes the follow-ups the bump needs:

  • go#575 removed the ctx argument from computeengine.NewClient. internal/purchase/gcp_history_cost_test.go now uses the new signature.
  • go#603/fix(plans): a plan with no matching recommendations schedules zero-count purchases instead of failing #609 renamed provider.ProviderConfig to provider.Config and kept the old name as a deprecated alias. staticcheck SA1019 flagged all 40 uses, so they now use provider.Config. This is a type alias, so behaviour does not change. mock.AnythingOfType reports the real type name, so the zonal_recs_test.go expectation now matches "*provider.Config". Without that change, TestCollectAWSForAccount_AssumedRolePassesExternalID panics with "Unexpected Method Call". The test's intent is unchanged.
  • The providerfactory bypass guard (TestNoRegistryBypassInProductionCode) only recognised a provider.ProviderConfig{Profile: ...} literal. It now recognises both names. I checked this by adding a temporary production file containing &provider.Config{Profile: "x"}: the guard flagged it, and the file was deleted afterwards.

Commit 2: fix(server). assembleLadderPlan now reports allocResult.NetExistingUSDPerHour (copied, not aliased) and no longer takes the layerStates parameter. Allocate sets this figure on every successful result, hold-only exits included. It covers only the configured layers, but Allocate already rejects a layer state outside them, so the scope is the same as before.

Behaviour changes pulled in by the bump

Everything in go that touches the four modules between the old pins and 80e9b846. The items the issue named come first:

go PR Module Change
#614 pkg/ladder AllocateResult.NetExistingUSDPerHour (used here). Net existing is now computed before the baseline-unavailable hold, so an inconsistent snapshot (expiring above existing) is now an error on that path, where it used to be a Hold.
#441 pkg/ladder New UsageBaseline.Basis (BaselineBasis). A residual baseline no longer has existing commitment subtracted a second time. Neither the platform nor the pinned AWS provider sets Basis yet, so runs keep the default total-demand basis and their behaviour is unchanged. An unknown basis value is now an error.
#471 providers/gcp GCP pricing fails closed. A region-less Cloud Storage SKU matches only when it is GLOBAL. Cloud SQL, Memorystore and Cloud Storage now error with "ambiguous catalog" when two matching SKUs give the same price slot different prices; before, the last one in the catalog won.
#628 pkg + providers/gcp 1-year and 3-year commitment SKUs get separate slots, so a catalog with both terms prices correctly instead of failing closed as ambiguous after #471. An unknown term errors.
#491 pkg/recfilter + providers/azure DuplicateChecker checks against GetCoveringCommitments when a client implements it. Azure savings plans billed to another subscription but applied to this one now suppress a matching recommendation, so they are not bought twice.
#627 providers/azure Pins Azure's own pkg requirement to the #491 selector. Without it, a standalone build never consulted the covering list.
#439 providers/azure VM reservations in a terminal state (cancelled, expired, failed, split, merged) skip the applied-scope check. Before, one such Shared reservation made GetExistingCommitments fail and blocked purchases for the subscription indefinitely.
#629 pkg/ladder A buffer-only layer that also has a pending reshape gets an informational Hold instead of a purchase.

Other changes in the range that alter behaviour:

Verification

All commands were run from the worktree with AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true.

Fail before, pass after. The new test is TestExecuteLadderRun_ReportsNetExistingCoverage in internal/server/ladder_existing_units_test.go. It runs the real executeLadderRun → Allocate → plan pipeline with existing 4 and expiring 2 commitment at rate 0.5 (8 OD gross, 4 OD net). There are two cases: an allocating run and a baseline-unavailable hold.

  • With origin/main's handler_ladder.go and the new pin, both cases FAIL: Max difference between 4 and 8 on existing_usd_hr and on the plan JSON, and gap reconciles with target - existing fails with 2 vs 6.
  • With this PR, both cases PASS (-race).

Checks

  • go build ./..., go vet ./..., go vet -tags integration ./... and (cd tests/e2e && go vet -tags e2e ./...): all clean.
  • golangci-lint v2.10.1 on ./..., with and without --build-tags integration: 0 issues. Before the provider.Config rename it reported 40 SA1019 issues.
  • gocyclo -over 10 on the changed non-test files: no output.
  • go test -race -short -count=1 ./... (full unit suite): all 45 packages pass. Before the mock fix, internal/scheduler failed as described above.
  • go test -race -tags integration -count=1 ./internal/{api,purchase,scheduler,server,config,providerfactory}/: purchase, scheduler and providerfactory passed on the first run. The host was heavily loaded by other agents (load average about 48), so the first api/server/config runs had testcontainers Postgres fail to start within its 30s startup timeout (wait until ready ... context deadline exceeded), and every failing test failed only there, before any test code ran. On rerun, internal/config passed as a full package (ok ... 636s). The server tests that had hit the container timeout (TestHealth_ConfigStoreAfterFailedReinitialization, TestEnsureDB_IsLambdaSingleSourceOfTruth, TestCheckConfigStore_DBFailureHidesRawError) passed when rerun with -run (ok). So did the api ones (TestApproveRIExchange_Integration_SessionMissingIs404, TestIntegration_HistorySummary_TotalsCoverAllInScopeRows, TestIntegration_HistorySummary_MatchesListedRowsUnderTheCap). All other server and api integration tests, including the ladder lock tests, passed in the full-package runs.
  • Secrets scan on the changed files: clean; planted key flagged. scripts/test-git-secrets-allowlist.sh: 183 PASS, 0 FAIL.

Not verified: the go-side behaviour changes listed above were only exercised through the platform's tests, with mocks, fakes and testcontainers Postgres. No real cloud accounts were used.

🤖 Generated with Claude Code

IAmJSD and others added 2 commits October 26, 2026 08:57
The platform pinned pkg to a commit from before go#614, so Allocate's
NetExistingUSDPerHour was not available to the ladder handler (LeanerCloud#243).
Move pkg and providers/{aws,azure,gcp} to go main, which also brings in
go#441, LeanerCloud#471, LeanerCloud#491, LeanerCloud#439, LeanerCloud#627, LeanerCloud#628 and LeanerCloud#629.

The bump needs three mechanical follow-ups:
- go#575 dropped the ctx argument from computeengine.NewClient; the GCP
  history-cost test is updated to the new signature.
- go#603/LeanerCloud#609 renamed provider.ProviderConfig to provider.Config and left
  the old name as a deprecated alias. staticcheck SA1019 flags every use,
  so all references move to provider.Config. mock.AnythingOfType reports
  the real type name, so the zonal-recs mock now matches
  "*provider.Config".
- The providerfactory bypass guard matched only the ProviderConfig name;
  it now recognises both names so a Profile literal is still caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
assembleLadderPlan summed each layer's gross ExistingUSDPerHour into the
plan, ladder_runs.existing_usd_hr and the approval email, while Allocate
sized the gap against existing minus expiring. A layer with $8/hr OD of
existing of which $4/hr expires showed Existing $8/hr next to a gap of
$6/hr against a $10/hr target, which does not reconcile.

Report Allocate's NetExistingUSDPerHour instead (go#614), which is set
on every result including the hold-only exits, so the plan shows the
figure the engine used rather than a recomputed one.

Closes LeanerCloud#243

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added effort/l Weeks type/chore Maintenance / non-user-visible priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline impact/few Limited audience triaged Item has been triaged labels Oct 26, 2026
@coderabbitai

coderabbitai Bot commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@IAmJSD

IAmJSD commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE 8b64a354ce1c8a6fb82bae44c93b55cc86049a2c

Blocking:

  • none

Non-blocking:

Attacked:

  • Ladder change: read Allocate at the new pin. NetExistingUSDPerHour is set on every successful return, hold-only exits included, and is a copy. The plan copies it again, so there is no aliasing. A nil existing is unreachable on success. Other readers of existing_usd_hr: the timeline (display only) and the amend ceiling (above).
  • Fail-before: put main's internal/server/handler_ladder.go back in the scratch tree, at the new pin. TestExecuteLadderRun_ReportsNetExistingCoverage fails in both subtests (Max difference between 4 and 8 on the run and on the plan JSON; gap 2 vs 6). Restored, it passes.
  • Bump hygiene: go mod tidy -diff is clean, go mod verify passes, and go.sum changes only the 8 cloud-commitments-go lines (no transitive bumps). The provider.ProviderConfig -> provider.Config rename is a type alias. The bypass guard now matches both names.
  • Ran: go build ./...; go vet ./..., with and without -tags integration, plus e2e; go test -race -short -count=1 ./... (every package ok); go test -race -tags integration -count=1 on server, scheduler, providerfactory, purchase, api and config (all ok); golangci-lint v2.10.1 on the changed packages, with and without the integration tag (0 issues); gocyclo -over 10 on the changed non-test files (clean). For Closes chore(purchase/ladder/exchange): medium/low findings from the 2026-07-28 full review [platform part] #243, I spot-checked the other items on main (ctx-aware finalize sleep and its test, the PlanID == "" short-circuit, the findAWSAccount Enabled filter, 503 on a validation error, positive confirmation in the Azure probe). All are present, and fix(purchase,commitmentopts): close out the open #243 review findings #1265 is merged.

Body-stale:

  • none

Reviewer: rE2. 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.

@IAmJSD

IAmJSD commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE 8b64a354ce1c8a6fb82bae44c93b55cc86049a2c

Blocking:

  • none

Non-blocking:

Attacked:

  • Code: assembleLadderPlan now reads AllocateResult.NetExistingUSDPerHour. In go pkg/ladder/allocate.go:175 that is set on every successful result, so it's never nil after a nil error, and it's copied rather than aliased. Dropping the layerStates parameter loses nothing, because Allocate already rejects out-of-scope layer states.
  • Fail-before: with main's handler_ladder.go and the new pin, both subtests of TestExecuteLadderRun_ReportsNetExistingCoverage FAIL (4 vs 8 on existing_usd_hr and the plan JSON, 2 vs 6 on gap reconciliation). With the PR they PASS. Restored afterwards.
  • Bump fallout I checked against the platform's callers:
  • Ran: go build ./..., go vet ./..., go vet -tags integration ./..., tests/e2e vet with -tags e2e, go mod verify (all verified), go mod tidy (no diff). golangci-lint v2.10.1 on ./... and with --build-tags integration on the changed packages: 0 issues. Full go test -race -short -count=1 ./...: all ok. go test -race -tags integration -count=1 on internal/server, purchase, scheduler and providerfactory, plus -tags integration ./internal/api/: all ok.

Body-stale:

Reviewer: rE6. 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.

@IAmJSD

IAmJSD commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE 8b64a354ce1c8a6fb82bae44c93b55cc86049a2c

Addendum to my review above. After posting, I skimmed the earlier reviews and found one point I had missed. It comes from an earlier review, and I confirmed it in the code:

Blocking:

  • none

Non-blocking:

  • (from an earlier review) internal/config/store_postgres_ladder_amend_ceiling.go:30-35 and :116: the tranche-amend raise ceiling is LEAST(target, baseline*coverage/100) - existing_usd_hr, taken from the latest run. This PR changes that column from gross to net of expiring, so on new runs the ceiling is looser by the expiring amount, and the comment "existing is gross, so C errs low" is now wrong. I agree the net value is the consistent one, because it's the room Allocate sized the gap against. Still, it's a cap change on a money path. The comment should be updated, and the PR body should say the amend ceiling follows the column. Older ladder_runs rows keep the gross value, which is stricter, until the next run.

Attacked: read checkLadderConfigCeiling / loadLadderCeiling at this head to confirm they consume existing_usd_hr.

Body-stale: as in my review above. Also, the body doesn't mention the amend-ceiling change.

Reviewer: rE6. 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.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/l Weeks impact/few Limited audience priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/chore Maintenance / non-user-visible urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(purchase/ladder/exchange): medium/low findings from the 2026-07-28 full review [platform part]

1 participant