Repository navigation
fix(history): sort future pending ramp steps after due rows in the actionable fetch - #1038
Conversation
…tionable fetch GetExecutionsByStatuses ordered by scheduled_date DESC with a MaxListLimit (1000) clamp. Plan creation writes up to 52 pending ramp steps per plan, all dated in the future, so about 20 plans fill the cap with steps months ahead and a pending approval due today never comes back (truncated was set, but the row was lost). Pending rows with a future scheduled_date now sort after every other row, soonest first among themselves, so the cap drops the farthest steps. Every other row keeps scheduled_date DESC; terminal statuses never match the predicate, so the terminal class is unchanged. The History handler re-sorts the merged list by timestamp, so only which rows survive the cap changes. The shared mock mirrors the new order. Closes LeanerCloud#680 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 6 billable files and costs up to $1.50. Or wait 59 minutes for your next included review. View limit details
Comment |
Independent review verdict: CLEAN at 44d06e5Reviewer: fresh Claude agent (Sonnet, high effort), not the author, cold brief. The PR body was treated as a claim. Coordinator: claude-cc-reviewer. Blocking findings: none. New ORDER BY (
Notes (non-blocking):
What was attacked and how:
Gate:
|
|
Gate record for 44d06e5 (origin/main CLAUDE.md "Review gate"):
Marking ready (owner-authorized) and merging at 44d06e5 with --match-head-commit. |
|
Gate record for 44d06e5 (origin/main CLAUDE.md "Review gate"):
Merging at 44d06e5 with --match-head-commit. |
POST /api/auth/forgot-password only had the per-email bucket, so one client could walk any number of addresses, one request each, with no total volume cap. Add a forgot_password_ip bucket (10 per 15 minutes per IP, the same budget as reset_password) checked before the body is parsed. It keys on the request's SourceIP like every other per-IP bucket (rightmost, proxy-appended X-Forwarded-For entry on the HTTP server) and fails open like the per-email bucket (02-M1). The comment/config mismatch from the issue was already reconciled to 10 per 5 minutes in #1038; the handler comment now names both buckets. Closes #13 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Root cause
GetExecutionsByStatuses(internal/config/store_postgres.go) ordered byscheduled_date DESCwith theMaxListLimit(1000) clamp. Plan creation (createPurchaseExecutionsTx) writes every ramp step up front as apendingrow dated in the future, up to 52 per plan. About 20 plans therefore fill the actionable fetch (#684) with steps months ahead, and a pending approval due today falls past the cap.truncatedwas set, but the row was lost.Fix
Only the ORDER BY changes:
scheduled_date DESC, so the farthest-future ramp steps come first and fill the cap.scheduled_date DESC(due and overdue pending rows,notified,scheduled,approved,running,paused, whatever their date). Future pending steps come after them, soonest first, so when the cap is hit it drops the farthest steps.pendingpredicate, so the terminal class keeps its order (pinned by a test that includes a canceled step with a future date).notifiedrows with a future date stay in the first group because their approval link is already out. Only not-yet-duependingsteps go to the back.MockConfigStore.GetExecutionsByStatuses(internal/mocks/stores.go) mirrors the new order so handler tests match the real store. I updated the interface and method docs.Cap and
truncated: unchanged. The actionable class is still clamped atMaxListLimit, andtruncatedstill fires when the fetch returns 1000 rows. The handler has no cursor or paging today (#674), so there are no cursor semantics to keep. A future keyset cursor would have to use the same three-part sort key.Migration / index
No migration. The tier expression can't be served by an index order, so Postgres sorts the actionable rows (status-filtered) with a top-N heapsort bounded by the LIMIT. That set is bounded by plans × ramp steps plus in-flight rows (thousands), not by terminal history volume. I didn't think an expression index was worth a migration for that.
UI impact
There is no visible change to ordering: the History handler re-sorts the merged list by timestamp. Only which rows survive the cap changes. A due approval now always shows. Once more than 1000 actionable rows exist, the dropped rows are the farthest-future ramp steps, where before they were the most recent due ones. The truncation banner behaves as before. No frontend change.
Closes #680
Verification (local, macOS; tests run with
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null)TestIntegration_GetExecutionsByStatuses_DueRowSurvivesFutureRampSteps: 20 real plans × 52 future pending steps (1040), plus a pending row due an hour ago, an overdue one, a futurenotifiedrow and a nearscheduledrow. The result has 1000 rows (the cap is still hit, sotruncatedholds). The four actionable rows come first inscheduled_date DESC, then future pending steps ascending. It FAILS on main (the head was all far-future steps) and passes with the fix.TestIntegration_GetExecutionsByStatuses_TerminalOrderUnchanged: failed, expired and a canceled step with a future date stay newest first. The existingRowClassestest passes unchanged.TestHandler_getHistory_DueApprovalNotCrowdedOutByFutureRampSteps(throughgetHistorywith the store-mirroring mock): the due row is returned, the soonest step is kept, the farthest is dropped, there are 1000 purchases, andtruncated: true. It FAILS on main and passes now.notifiedto the back tier (integration test); addingcanceledto the tier predicate (terminal test); turning off the tier in the mock (handler test).go build ./...,go vet ./...andgo vet -tags integration ./...are clean. golangci-lint v2.10.1./...reports 0 issues, with and without--build-tags integration.gocyclo -over 10on the changed non-test files is clean.internal/config,internal/api,internal/mocks,internal/analytics,internal/server,internal/purchase. Integration tests pass: the fullinternal/configpackage (-tags integration) andinternal/purchase -run RampStep.--register-aws, the only hit is an existing line on main (store_postgres_pgxmock_test.go:1129, a comment with a placeholder account id, authored in aed37fa). This PR adds no hits.scripts/test-git-secrets-allowlist.shreports 42 PASS.now()andtimestamptzcompare absolutely, so the timezone shouldn't matter).Out of scope
executions_limitstill reports the terminal cap (100). Wording follow-up in feat(frontend): Load more on the History view, Approval Queue from all loaded pages #676.🤖 Generated with Claude Code