Skip to content

feat(ladder): staggered tranche building from allocations - #1341

Merged
cristim merged 3 commits into
feat/ladder-allocatefrom
feat/ladder-tranches
Jul 3, 2026
Merged

feat(ladder): staggered tranche building from allocations#1341
cristim merged 3 commits into
feat/ladder-allocatefrom
feat/ladder-tranches

Conversation

@cristim

@cristim cristim commented Jul 3, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds BuildTranches (pkg/ladder/ladder.go): splits each Allocation from Allocate into (a) a buy-now ActionPurchase for the ramp step with AfterDays == 0 and (b) future Tranche rows for delayed steps (FireAfter = Now + AfterDays), so commitment terms expire staggered instead of bunching. A fully delayed ramp (no day-0 step) is valid and produces zero buy-now actions.
  • Exact big.Rat step arithmetic: every step amount is clamped to the remaining unallocated gap and the last step receives the exact leftover, so per-allocation totals reconstruct the gap exactly for every RampSchedule.Validate-passing schedule, including fraction sets whose exact rational sum slightly exceeds 1 within the validator epsilon (regression-tested with {0.5, 0.5000000008, 1e-10}).
  • Tranche rows are now fully self-describing: new AmountUSDPerHour (lossless big.Rat.RatString() encoding for DB round-tripping), Layer, Term, and PaymentOption fields with Validate coverage, so an executor can fire a tranche without any RunRecord/PlanJSON lookup; two equal-gap allocations on different layers stay distinguishable.
  • Deterministic by construction: injected clock (Now) and ID generator (NewID), no time.Now() or randomness inside; fail-loud validation naming each offending field.

Stacked on #1340 (which is stacked on #1338); auto-retargets as parents merge. Part of #1334 (phase 1 of #1333) - completes the phase-1 engine core.

Test plan

  • 204 table-driven tests pass in pkg/ladder (go test ./ladder/ -count=1, exit 0), including: exact staggering over a 3-step ramp with FireAfter date assertions, fully delayed and single-step schedules, multi-allocation determinism (double-run comparison with length asserts), inexact-fraction and tiny-gap remainder exactness, epsilon-overshoot clamping, tranche self-description (Layer/Term/PaymentOption stamping and distinguishability), sequential injected IDs, and 11 fail-loud validation subcases.
  • All gates exit 0: go build ./..., go vet ./ladder/, gofmt -l ladder/ (empty), golangci-lint run --config ../.golangci.yml ./ladder/..., gocyclo -over 10 on non-test files (nothing over threshold).
  • Every produced PlannedAction and Tranche passes its own Validate (asserted explicitly in a shared test helper).
  • Package is still inert: no callers until PR 8 wires the engine; no runtime behavior changes elsewhere.

Summary by CodeRabbit

  • New Features

    • Added support for building purchase plans with immediate and scheduled tranches from a ramp schedule.
    • Tranches now carry more complete details, making them easier to execute and review on their own.
  • Bug Fixes

    • Improved validation to catch incomplete or invalid tranche details earlier.
    • Fixed rounding edge cases so allocated amounts now match expected totals exactly.
  • Tests

    • Added broader coverage for ramp timing, ID assignment, validation, and edge cases.

@cristim cristim added triaged Item has been triaged priority/p1 Next up; this sprint severity/medium Moderate harm urgency/this-quarter Within the quarter impact/many Affects most users effort/m Days type/feat New capability labels Jul 3, 2026
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 14 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fa41c71f-0a69-4305-af76-b7189c59f8a8

📥 Commits

Reviewing files that changed from the base of the PR and between 6b4eb50 and 328743e.

📒 Files selected for processing (4)
  • pkg/ladder/ladder.go
  • pkg/ladder/ladder_test.go
  • pkg/ladder/store.go
  • pkg/ladder/store_test.go
📝 Walkthrough

Walkthrough

Adds a new pkg/ladder/ladder.go implementing BuildTranches, which validates input and converts allocations into buy-now actions and scheduled tranches using exact rational arithmetic. Extends Tranche with self-describing execution fields (AmountUSDPerHour, Layer, Term, PaymentOption) and corresponding validation, plus tests for both.

Changes

Ramp Tranche Building and Self-Describing Tranches

Layer / File(s) Summary
Input/output contracts and entry point
pkg/ladder/ladder.go
Defines TrancheInput/TrancheResult types and BuildTranches with input and allocation validation.
Step computation and routing
pkg/ladder/ladder.go
Implements buildTrancheResult, processAllocation, appendStep, and computeStepAmount using exact big.Rat arithmetic with clamping.
Buy-now and future tranche builders
pkg/ladder/ladder.go
Adds stepRationale, buildBuyNowAction, and buildFutureTranche with Validate-based error wrapping.
BuildTranches test suite
pkg/ladder/ladder_test.go
Covers multi-step ramps, determinism, ID/RunID stamping, rationale content, data source propagation, validation failures, and epsilon-overshoot clamping.
Tranche storage model extension
pkg/ladder/store.go, pkg/ladder/store_test.go
Adds AmountUSDPerHour, Layer, Term, PaymentOption fields to Tranche, new validateExecutionFields/validateAmountUSDPerHour helpers, updated interface docs, and extended validation tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant BuildTranches
  participant processAllocation
  participant computeStepAmount
  participant appendStep
  Caller->>BuildTranches: TrancheInput
  BuildTranches->>processAllocation: allocation
  processAllocation->>computeStepAmount: gap, priorSum, fraction
  computeStepAmount-->>processAllocation: step amount
  processAllocation->>appendStep: step amount, AfterDays
  appendStep-->>BuildTranches: BuyNow action or Tranche
  BuildTranches-->>Caller: TrancheResult
Loading

Related issues: None found in the provided context.

Related PRs: None found in the provided context.

Suggested labels: enhancement, needs-review

Suggested reviewers: None identified from provided context.

Poem
A rabbit hops through ramps of gold,
Splitting gaps as rationals unfold,
Buy-now here, a tranche laid there,
Each step exact beyond compare,
Self-describing, no plan to seek—
This burrow's math is neat and sleek. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: staggered tranche construction from allocations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ladder-tranches

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

@cristim

cristim commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
pkg/ladder/store.go (1)

226-238: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

AmountUSDPerHour accepts non-canonical rationals. big.Rat.SetString also parses decimal/exponent forms, so the validator allows inputs beyond the documented RatString() encoding. If canonical a/b or a form matters for storage/equality, reject or normalize non-canonical inputs; otherwise update the docs to match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/ladder/store.go` around lines 226 - 238, The AmountUSDPerHour validator
in Tranche.validateAmountUSDPerHour currently accepts non-canonical rationals
because big.Rat.SetString parses decimals and exponent forms too. Tighten the
validation so only the intended canonical RatString format is accepted, or
normalize the parsed value before storing it; if you choose to keep broader
parsing, update the documented contract and error text around
validateAmountUSDPerHour and AmountUSDPerHour to match the actual accepted
formats.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/ladder/ladder.go`:
- Around line 190-221: The ramp fraction handling in computeStepAmount can panic
because NaN/Inf values may slip past RampSchedule.Validate and then reach
big.Rat.SetFloat64. Add an explicit non-finite check either in
RampSchedule.Validate or right before the conversion in computeStepAmount, and
reject or clamp invalid fractions before calling SetFloat64. Use the
RampSchedule.Validate and computeStepAmount symbols to keep the fix aligned with
the existing validation flow.

---

Nitpick comments:
In `@pkg/ladder/store.go`:
- Around line 226-238: The AmountUSDPerHour validator in
Tranche.validateAmountUSDPerHour currently accepts non-canonical rationals
because big.Rat.SetString parses decimals and exponent forms too. Tighten the
validation so only the intended canonical RatString format is accepted, or
normalize the parsed value before storing it; if you choose to keep broader
parsing, update the documented contract and error text around
validateAmountUSDPerHour and AmountUSDPerHour to match the actual accepted
formats.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a6f593be-742d-42fa-9b21-ee9a300e7792

📥 Commits

Reviewing files that changed from the base of the PR and between f2754f0 and 6b4eb50.

📒 Files selected for processing (4)
  • pkg/ladder/ladder.go
  • pkg/ladder/ladder_test.go
  • pkg/ladder/store.go
  • pkg/ladder/store_test.go

Comment thread pkg/ladder/ladder.go
@cristim
cristim force-pushed the feat/ladder-allocate branch from 2d7f6cb to 03fffc4 Compare July 3, 2026 13:51
cristim added 3 commits July 3, 2026 15:52
…dule

Split each Allocation into a buy-now purchase for the immediate ramp step
and future Tranche rows per delayed step, so commitment terms expire
staggered. Step amounts use exact big.Rat arithmetic with a last-step
remainder and an overshoot clamp, so totals always reconstruct the gap
exactly. Tranche rows are fully self-describing (Layer, Term,
PaymentOption, amount) and clock plus ID generation are injected. Part of #1334.
…ring

Reject empty or repeated IDs from the injected NewID across all tranches
of a run, since SaveTranches upserts by ID and a collision would silently
drop a scheduled purchase. Render step fractions with %.4g so small
percentages no longer show as 0% on positive purchases, require a
non-empty rationale on every input allocation, and align the tranche
amount validation docs with what big.Rat SetString actually enforces.
Switch TrancheInput and Tranche Term/PaymentOption fields from raw
strings to the ladder.Term and ladder.PaymentOption enums introduced on
feat/ladder-types, validate them via the enum Validate methods instead
of non-empty checks, and move all test fixtures to the typed constants.
Unknown enum values are now rejected at the tranche-building boundary in
addition to empty ones.
@cristim
cristim force-pushed the feat/ladder-tranches branch from 6b4eb50 to 328743e Compare July 3, 2026 13:57
@cristim

cristim commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim
cristim merged commit 50865b4 into feat/ladder-allocate Jul 3, 2026
4 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/many Affects most users priority/p1 Next up; this sprint severity/medium Moderate harm triaged Item has been triaged type/feat New capability urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant