Skip to content

fix(ladder): net tranches completed between the E read and the locked in-flight sum - #850

Merged
cristim merged 1 commit into
mainfrom
fix/843-e-race
Oct 13, 2026
Merged

cristim merged 1 commit into
mainfrom
fix/843-e-race

Conversation

@cristim

@cristim cristim commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Closes #843

What

PlanAndSaveLadderRun now adds completed tranches settled after ladderSettleAnchor(asOf) (asOf minus ladderSettleSkewMargin, 1 min) to the locked in-flight figure, using the shared settledLadderCommit predicate from #849. asOf is the handler's now, captured before GetLayerStates (E), so a tranche that goes fired->completed between the E read and the locked SUM is netted. If E already contains it, it is counted twice: a safe, bounded under-buy that heals next cadence. Never a miss.

Safety condition

This is safe only once the visibility adapter (executor PR5a) lands: a tranche must be marked completed only after its commitment is visible through the same source as E, and visibility must not revert. Nothing completes tranches today.

Verification (real PostgreSQL 17, local macOS; Linux via CI)

  • Handler-level race test (TestLadderB1_TrancheCompletingBetweenEReadAndLockedSumIsNetted; real store and schema, FIXTURE capability that completes the tranche inside GetLayerStates and returns stale E): FAILS on origin/main (re-plans 5, want 0), passes with the fix.
  • Store-level boundary test: settled_at == anchor not counted, +1us counted, inside the skew margin counted, failed/canceled never, fired added.
  • Mutants killed: drop settled term, anchor = time.Now(), margin = 0 (store test only). Statement-order swap is not testable; documented.

Labels mirror #843.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved ladder commitment tracking when a tranche completes during a run, helping prevent completed amounts from being planned again.
    • Accounted for recently settled tranches when calculating in-flight commitments, reducing the chance of an incorrect under-buy.

… in-flight sum

PlanAndSaveLadderRun now adds completed tranches settled after
ladderSettleAnchor(asOf) to the locked in-flight figure. asOf is captured
before the layer-state (E) read, so a tranche completing in between is netted
(counted twice if E already has it: a safe under-buy). The anchor is asOf minus
ladderSettleSkewMargin to cover app/DB clock skew. Safe only once the
visibility adapter (executor PR5a) lands.

Closes #843

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 13, 2026
@coderabbitai

coderabbitai Bot commented Oct 13, 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: e7cb24da-a216-4982-919f-a9529ae9eaf8
📥 Commits

Reviewing files that changed from the base of the PR and between 438523e and 42ae500.

📒 Files selected for processing (6)
  • internal/config/interfaces.go
  • internal/config/store_postgres_ladder.go
  • internal/config/store_postgres_ladder_lock.go
  • internal/config/store_postgres_ladder_settled_plan_integration_test.go
  • internal/server/handler_ladder.go
  • internal/server/handler_ladder_immediate_integration_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 2 reviews per hour.

📝 Walkthrough

Ladder planning now combines in-flight commitments with eligible settled tranche commitments. Integration tests cover settlement timing and verify that a completed tranche is not re-planned during a subsequent run.

Changes: Ladder commitment netting

Layer / File(s) Summary
Combine in-flight and settled commitments
internal/config/interfaces.go, internal/config/store_postgres_ladder.go, internal/config/store_postgres_ladder_lock.go, internal/config/store_postgres_ladder_settled_plan_integration_test.go
PlanAndSaveLadderRun now adds in-flight commitments to settled commitments read using a one-minute skew-adjusted anchor. The contract documents the timing and visibility assumptions. An integration test covers settlement timing and tranche states.
Verify completed tranche handling
internal/server/handler_ladder.go, internal/server/handler_ladder_immediate_integration_test.go
The handler comment describes how settlement timing affects netting. An integration test completes a tranche during the next run’s layer-state read and verifies that the run creates no tranches.

Priority: ➖ Normal

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

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 42ae5

No confirmed issue currently blocks merging. The production visibility ordering has not been verified.

Pre-merge checks · Passed 5
Passed Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 6 files.
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.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: netting tranches completed between the existing-commitment read and the locked in-flight sum.
✨ Finishing Touches
📝 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 13, 2026

Copy link
Copy Markdown
Member Author

Gate verdict (Opus 5.5, money path): APPROVE at 42ae5008f2f8b90760d9a008fa83a0fb9981514a

Independent adversarial review of the full diff against base 438523ef (= merge-base, not BEHIND). Local evidence: git archive of the exact head, GOTOOLCHAIN=go1.26.9, throwaway local PostgreSQL 17 (macOS), under the platform test-suite lock.

Correctness

  • Order: lockedLadderCommit (store_postgres_ladder_lock.go:163-174) runs inFlightLadderCommit then settledLadderCommit, both inside withLadderConfigLock (READ COMMITTED, after pg_advisory_xact_lock). Interleavings of a concurrent fired->completed commit C: C before stmt1 -> settled term (settled_at >= visibility time > E read >= asOf > anchor); C between stmt1 and stmt2 -> counted by both (safe under-buy); C after stmt2 -> counted as fired. settled_at is stamped at UPDATE time but visibility precedes it, so a stamp <= anchor implies E already had it. Never missed. Reverse order misses C between the statements; the comment (:156-162) documents it.
  • Anchor: settled_at > $2 strict (store_postgres_ladder_settled.go), anchor = ladderSettleAnchor(asOf) = asOf - 1m. asOf still drives ladderGraceCutoff and the run.StartedAt check. Const comment states 1m << cadenceDailyMin (20h, handler_ladder.go:27).
  • now is captured once at handler_ladder.go:125 before runLadderConfigs; the only production GetLayerStates call is handler_ladder.go:315 inside executeLadderRun, after it. Only caller of PlanAndSaveLadderRun is :333.
  • Docs updated (interfaces.go, lock.go, GetInFlightLadderCommitUSDHr, handler netting comment); visibility dependency on executor PR5a stated. No migration, interface signature or mock change; with no completion after the anchor the settled term is 0 and planner input is unchanged.

Local evidence

  • T2 TestLadderB1_TrancheCompletingBetweenEReadAndLockedSumIsNetted on origin/main code + PR test: FAIL Max difference between 0 and 5 ... difference was -5 (re-plans 5). At head: PASS.
  • At head, -count=5: TestLadderB1_* ok; TestPostgresStoreDB_PlanAndSaveSettledTerm|TestPostgresStoreDB_LadderSettledAt ok.
  • go build ./..., go vet (plain and -tags integration) on config/server ok; unit go test ./internal/config ./internal/server ./internal/mocks ok. git-secrets (--register-aws) BSD and GNU grep: exit 0 on all 6 files.
  • Mutants (one run): killed: drop settled add (store+T2), > to >= (store), anchor=time.Now() (store+T2), anchor=time.Now()-margin (store), margin=0 (store), anchor=asOf+margin (T2).

Non-blocking follow-ups

  • Survivor: drop status = 'completed' from the feat(ladder): stamp ladder_tranches.settled_at when a tranche completes #849 predicate. Equivalent today (only the completed edge stamps settled_at and completed is terminal); a test row of failed status with a non-NULL settled_at would pin it.
  • Survivor: anchor = asOf - 1h. The store test derives its boundaries from ladderSettleAnchor itself, so the margin value is not pinned against asOf (over-wide only means more under-buy).
  • Survivor (expected, documented): statement-order swap.
  • Nit: handler_ladder.go:323 says "tranches completed after now"; precisely it is after now - ladderSettleSkewMargin.
  • CodeRabbit: no actionable comments.
  • CI: all required checks green at this SHA; mergeStateStatus CLEAN.

@cristim
cristim merged commit b85908a into main Oct 13, 2026
25 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.

fix(ladder): E read vs fired->completed hand-off race over-buys once executor lands

1 participant