Skip to content

fix(ladder): persist immediate ramp steps as tranches, plan JSON display-only (B1) - #826

Merged
cristim merged 1 commit into
mainfrom
feat/ladder-immediate-tranches-b1
Oct 12, 2026
Merged

cristim merged 1 commit into
mainfrom
feat/ladder-immediate-tranches-b1

Conversation

@cristim

@cristim cristim commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Refs #118
Refs go#36

Ladder B1: immediate (AfterDays==0) ramp steps are persisted as tranches so in-flight netting sees them. Nothing here enables purchasing: no executor exists, the flags stay off, and the plan JSON is display only (the only future purchase path is tranche -> purchase_executions).

Changes

  • go.mod/go.sum: cloud-commitments-go pkg, providers/aws, azure, gcp bumped from 9dae64b to ad67030 (real pseudo-version v0.0.0-20261012205319-ad67030387e4), no replace.
  • internal/server/handler_ladder.go: TrancheResult.BuyNow no longer exists, and every ramp step arrives as a Tranche, so the immediate one is persisted with scheduled_date = run start (immediate = FireAfter <= run start; no migration). Plan JSON: the ActionPurchase amount is the immediate step's amount only (a layer with no immediate step has no purchase action). total_hourly_commit is the sum of the persisted tranche amounts, not the allocation gap. Amounts are stored unchanged: on-demand-equivalent $/hr until go#349 converts them (documented in code, not changed here).
  • The UI text "Ladder purchases are not executed yet" is untouched and stays true.

Go commits pulled in by the bump (9dae64b..ad67030)

Evidence

Real Postgres 17 (throwaway local DB, migrations applied; LADDER_B1_TEST_DATABASE_URL, falls back to the Postgres container helper in CI): go test -tags integration ./internal/server -run LadderB1 passes 8 (incl. subtests).

  • ramp [0:.5, 30:.5], gap 10 persists two tranches 5/5, the immediate one with scheduled_date = run start; plan purchase amount = 5, total_hourly_commit = 10 = sum of rows (plan numbers equal persisted rows).
  • next run at 1h and at 24h-1us nets the immediate row (plans 0); at exactly 24h and 24h+1us the row is canceled and only that 5 is re-planned (grace boundary table: counted xor canceled).
  • daily re-run for 41 days never leaves more live (scheduled+fired) commitment than target-E=10 and ends at 10.
  • example ramp after_days:0 fraction:1.0 persists one tranche of 10.
  • Mutations (all caught): skip step-0 rows (4 tests fail), plan amount = full gap (1), grace cutoff = asOf (2).
  • FIXTURE/mock labels: the ladder capability (baseline, layer states) is a fixture; TestExecuteLadderRun_ImmediateStepIsTrancheAndPlanShowsOnlyIt and the updated Healthy/MaxActions tests use a mock store.
  • go test ./internal/... 7340 passed, make lint 0 issues, make tidy-check ok, pre-commit hooks pass, git ls-files -z | xargs -0 git secrets --scan clean under BSD and GNU grep with --register-aws.

Not in this PR

Persist-lock PR (in-flight read inside the persist tx), the reconciler, go#349 unit conversion, the executor.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Immediate ramp steps now appear consistently in purchase plans and persisted commitment tranches, with run totals matching the saved tranches.
    • Future-only ramp steps no longer appear as immediate purchase actions.
    • Subsequent runs correctly account for prior immediate tranches within the grace period.
    • Repeated daily runs keep scheduled or fired commitments within the target and bring them to the target.

…an display-only

Bump cloud-commitments-go pkg and the providers to ad67030, where every ramp
step including AfterDays==0 is a Tranche and TrancheResult.BuyNow is removed.

- persistLadderRun already persists every returned Tranche; the immediate step
  now arrives as a row with scheduled_date = run start, so the next run's
  in-flight netting (grace-bounded, #824) sees it.
- The plan JSON purchase action carries only the immediate step's amount, not
  the allocation gap; a layer with no immediate step has no purchase action.
- total_hourly_commit is the sum of the persisted tranche rows.
- Amounts are stored unchanged (on-demand-equivalent $/hr until go#349).

No executor exists and flags stay off; the plan is not a purchase path.

Refs #118
Refs go#36

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@cristim cristim added urgency/this-sprint Within the current sprint triaged Item has been triaged priority/p1 Next up; this sprint severity/high Significant harm impact/few Limited audience effort/m Days type/bug Defect labels Oct 12, 2026
@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: LeanerCloud/cloud-commitments-platform/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 7053a646-0efd-48f2-9e27-0be0845888f2

📥 Commits

Reviewing files that changed from the base of the PR and between ca01672 and aedb5ae.


⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum

📒 Files selected for processing (4)
  • go.mod
  • internal/server/handler_ladder.go
  • internal/server/handler_ladder_immediate_integration_test.go
  • internal/server/handler_ladder_test.go

Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour.



📝 Walkthrough

Walkthrough

Ladder runs now derive immediate purchase actions from tranches firing at or before run start and calculate persisted hourly commitment totals from saved tranche rows. Tests cover immediate and delayed ramps, grace-boundary reruns, and target limits. Four module requirements use a newer shared pseudo-version.

Changes

Ladder tranche accounting

Layer / File(s) Summary
Immediate actions and persisted totals
internal/server/handler_ladder.go
The run derives immediate amounts from tranches firing at or before run start. Display purchase actions use those amounts, and persisted hourly totals sum the saved tranche rows. Invalid immediate tranche amounts return an error.
Tranche persistence and rerun tests
internal/server/handler_ladder_test.go, internal/server/handler_ladder_immediate_integration_test.go
Unit and PostgreSQL integration tests check immediate and delayed tranche persistence, display actions, grace-boundary reruns, and live commitment totals against the target.

Shared module version update

Layer / File(s) Summary
Module requirements
go.mod
The pkg, providers/aws, providers/azure, and providers/gcp modules use a newer shared cloud-commitments-go pseudo-version.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix


Merge Risk: ⚪ Minimal · up to aedb5

This change makes ladder plan display and persisted totals follow the saved tranche rows. Purchasing stays disabled, so production impact is limited. No merge-blocking risk was identified.

Pre-merge checks · Passed 4 · Failed 1

Failed Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 3 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main changes: persisting immediate ramp steps as tranches and keeping plan JSON display-only.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

Full details: Docstring Coverage

Explanation

Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 3 files. (1 skipped: 1 unsupported.)


  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@cristim

cristim commented Oct 12, 2026

Copy link
Copy Markdown
Member Author

Independent gate review (Opus 5.5, claude-opus-5-5): APPROVE at aedb5ae

Money path (ladder netting). Nothing here purchases: executeLadderRun never calls PurchaseLayer/ReshapeBuffer, no non-test caller of ClaimDueLadderTranches exists, LadderingEnabled/LadderExecutionEnabled untouched, and no code reads plan JSON ActionPurchase as an executable instruction (only the timeline UI renders rationales).

go.mod/go.sum: ad67030387e4 exists on go main (committed 2026-10-12T20:53:19Z, matching pseudo-version v0.0.0-20261012205319-ad67030387e4, all four modules). No replace, no vendor, go mod tidy -diff clean, go mod verify ok. gh api compare 9dae64b...ad67030 = 7 commits, all 7 listed in the PR body. go #344 risk checked: Azure savingsplans.GetRecommendations returns empty, so platform never persists Azure SP recs and the new currency check cannot fire on any platform path. #347/#341 (RDS family sizing) not called by platform; #348 only changes EC2 layer coverage states.

Persistence: scheduled_date is TIMESTAMPTZ; BuildTranches sets the step-0 FireAfter = Now; netting (> now-24h) and the sweep (<= now-24h) use the same run.StartedAt, so each immediate row is counted for 24h then canceled, never both or neither. Immediate rows have past dates, so the amend endpoint (scheduled_date > clock_timestamp()) and the editor ("scheduled date has passed") reject edits. total_hourly_commit = sum of rows also tightens the amend cap for new runs; legacy runs keep their old totals (unchanged behaviour).

Local evidence (throwaway Homebrew PostgreSQL 17.11 on 127.0.0.1, db ladder_b1_gate, go1.26.9, -tags integration):

  • At head: all 7 TestLadderB1* (incl. 4 grace subtests) and 10 TestExecuteLadderRun* pass.
  • Fails before: the PR's integration tests on origin/main ca01672 (old go pin): 4 of 4 B1 tests FAIL (immediate row missing; 1h re-run re-plans 2.5; example ramp persists 0 rows). Handler-only revert with the new go pin: the plan/total tests fail ([]float64{10} vs {2.5}, 5 vs 10), netting tests pass (persistence comes from go#351).
  • Reviewer-written variants (not committed): target raised 10 -> 20 at day 15 of a 42-day daily run: live never exceeds the current target, ends at 20. Three-step ramp [0:.4, 1:.3, 30:.3] run 3x/day for 42 days: never exceeds 10, ends at 10; plan shows 4 (day-0 step only), total 10. Duplicate AfterDays==0 steps are rejected by ramp validation.
  • Mutants on changed handler lines: 6/7 killed (includes-future, strict-before, plan=full gap, total excludes immediate, sum skips last, scheduled_date shifted). Survivor (non-blocking): replacing the per-layer Add with Set in immediateTrancheAmounts is equivalent today because ramp validation allows only one AfterDays==0 step per layer.
  • go test ./... at head exit 0; go test ./internal/... on head merged with current main ab34f48 exit 0; make lint 0 issues; git ls-files -z | xargs -0 git secrets --scan clean under BSD grep and GNU grep 3.12.

CI: green on the SHA incl. Integration Tests and Unit Tests. The first pre-commit run failed on the Trivy installer download (transient, before any hook ran); rerun passed. mergeStateStatus CLEAN. Labels mirror #118; body says Refs #118 / Refs go#36.

Non-blocking notes: (1) with no executor, the immediate row shows as Scheduled ("scheduled date has passed") and counts in the cumulative planned chart for 24h, then shows Cancelled; that matches the "not executed yet" banner. (2) Daily re-runs re-plan each expired immediate amount through the ramp again, so the live total sits a hair under target once the halving remainder drops below the engine's minimum (seen as 9.990235 of 10 on day 14); it converges when later steps land. Under-commit, not over.

@cristim
cristim merged commit 93eb871 into main Oct 12, 2026
26 of 27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/few Limited audience priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant