Repository navigation
fix(video): clamp the last poll so the 9-min budget cannot outlive its authorization - #111
Merged
Merged
Conversation
…s authorization The 540s budget was only 60s inside the 600s payment authorization at loop ENTRY. The loop body then added a 5s interval sleep and an unbounded 90s poll timeout, so a poll entered at 539.9s stayed in flight until 635s — 35s PAST validBefore, not 60s before it. Settlement happens server-side on the poll that answers "completed", so a poll outliving the authorization fails settlement for a video that actually generated. blockrun_music hit this exact failure (see CHANGELOG) and was fixed by raising the window to 600s; this restores video's side of that invariant. Lowering the budget instead would have defeated the PR: the production jobs it exists to support ran 479s, and staying inside 600s unclamped needs budget <= 445s. Clamping the last poll to the remaining budget keeps 540s available and bounds worst-case wall time at exactly 540s. - extract pollTimeoutFor(), so the clamp is unit-testable rather than buried behind a 402 + payment + submit chain the suite blocks at the network - name VIDEO_POLL_TIMEOUT_MS and VIDEO_PAYMENT_AUTH_SECONDS, and use the latter where the authorization is signed - replace the constants-only assertion (which restated 600 as a literal and ignored the interval and poll timeout entirely) with the real invariant plus a swept property test: a poll started at any reachable instant finishes on or before the deadline
VickyXAI
added a commit
that referenced
this pull request
Aug 28, 2026
…t the poll budget (#113) blockrun_video got this in #111; music has the same shape and the same latent bug, one budget bump away from firing. music measures its 240s poll budget from AFTER submit, but validBefore is counted from signing — and submit is allowed 95s. So the real worst case from the authorization's own origin is 95 + 240 + 5 + 90 = 430s. That fits inside 600s today, which is exactly why it looks fine and why the next person to raise MUSIC_POLL_BUDGET_MS the way #110 raised video's would walk straight into it: at 420s the sum is 610s and settlement starts failing for tracks that rendered. Naively copying video's fix (stamp startedAt before signing) would have been worse than the bug: the 240s budget would then absorb up to 95s of submit, leaving as little as 145s of polling for MiniMax tracks the code documents as taking 60-180s. So the loop takes the EARLIER of two deadlines instead — the poll budget, and the authorization minus settle-side slack. A fast submit keeps the full 240s window; a slow one shortens it rather than silently pushing polls past validBefore. - share pollTimeoutFor from src/utils/poll.ts, used by both tools - deliberately not in http.ts: four test files replace that module with a network sentinel, so a pure function there forces them to stub arithmetic - video.ts: repair the indentation break at the spend-booking site and drop the redundant inner !spendBooked guard already implied by the enclosing condition - cover the clamp in test/poll.test.ts and the music invariant in music-cost Co-authored-by: VickyXAI <vicky.fuyu@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #110. The 540s polling budget it introduced sits 60s inside the 600s payment authorization only at loop entry — the loop body then adds a 5s interval sleep and an unbounded 90s poll timeout, so a poll entered at 539.9s stays in flight until 635s, 35s past
validBefore.pollTimeoutFor(), bounding worst-case wall time at exactly 540sVIDEO_POLL_TIMEOUT_MSandVIDEO_PAYMENT_AUTH_SECONDS, and use the latter where the authorization is signed (it was a bare600literal)Why
validBefore = signingTime + maxTimeoutSeconds(@blockrun/llm/dist/index.js:109), and settlement happens server-side on the poll that answers"completed". A poll outliving the authorization fails settlement for a video that actually generated.This is not hypothetical —
blockrun_musicshipped the same bug. From CHANGELOG:That fix raised music to 600s to stay inside the window; #110 pushed video back out past it.
There's a secondary effect:
video.tsbooks local spend the instant it observesstatus === "completed", without checking settlement succeeded. Safe before, because expiry was unreachable; in the 600–635s tail it would record a ledger charge for USDC that never moved.Why not just lower the budget
Staying inside 600s unclamped requires
budget <= 445s, which fails the 479s production job #110 exists to support. The budget number isn't the lever — the unbounded tail is.Notes
pollTimeoutForis extracted rather than inlined so the clamp is testable. Inline it sits inside the handler behind a 402 + payment + submit chain thattest/video-models.test.tsblocks at the network by design (theNETWORK_ESCAPEsentinel).The tool description's "9 min hard cap" becomes accurate once this lands, so it's unchanged.
Validation
npm test— 343 passednpm run typecheck,npm run build— cleanMath.minto a bareVIDEO_POLL_TIMEOUT_MSand confirmed the new test fails (not ok 15 - the last poll is clamped...), so it catches the regression it guards