Repository navigation
fix(browser): a contract that held but never went quiet fell through as a clean stop — the deadline is not a stop (v1.31.1) - #191
Merged
Conversation
…as a clean stop — the deadline is not a stop; v1.31.1
`awaitContract` only left its loop early on "held AND quiet", but on the deadline exit it computed
`satisfied` from "held" alone, and the caller used that as `contractStopped`. A page whose clauses
all held while it kept mutating past tolerance for the whole timeout therefore skipped the quiet
window, the fallback settle, every `waitFor` gate and the final plateau — the stop-on-contract-alone
behaviour the design forbids, measured elsewhere to lose 99% of a product page's links.
Reproduced on a live product page with `quietMs: 60000` and `timeoutMs: 1500`: settle ended at
2000 / 1953 ms and `waitForResults` was empty on both runs.
`awaitContract` now returns `{ satisfied, stopped }`. `satisfied` is what the report carries (every
clause held); `stopped` is set only on the held-and-quiet exit and is what the settle acts on.
`ReadinessResult` gains `stopped`; the wire shape is unchanged. Regression test: a page that
satisfies its contract at once and never stops changing, with a `waitFor` gate as the witness that
the fallback ran — fails on 1.31.0, passes here.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request updates the headless-browser render library to distinguish between a readiness contract being 'satisfied' (all clauses held) and 'stopped' (all clauses held and the page went quiet). When a contract is satisfied but the page never goes quiet within its timeout, the render now correctly falls back to the ordinary settle instead of stopping prematurely. These changes include updates to the ReadinessResult type, the awaitContract function, documentation, and a new integration test simulating a churning page. There are no review comments to address, and I have no additional feedback to provide.
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.
Pre-deploy review of the readiness contracts as they stand on
main(browser 1.31.0, armed downstream in render-servicekohlsv1.25.0-kohls). One defect in the armed path, reproduced on a live page, fixed here with a regression test. Browser v1.31.1; no plugin change.The defect
awaitContractstops a render on "every clause holds and the page has been quiet forquietMs" — the second half is what protects the rails and everything else no clause names, and the design forbids stopping on the contract alone (measured: 99% of a product page's links lost).But the loop had one exit that dropped the second half. It only
breaks early onheld && quiet; when the deadline passed with every clause true and the page still mutating past tolerance, it fell out of the loop, computedsatisfiedfromheldalone, and the caller used that ascontractStopped. The render then skipped the quiet window, the fallback settle, bothwaitForgates andfinalDomStable, and snapshotted a page that was still changing.Reproduced with the render-service harness on a live product page, desktop,
quietMs: 60000(unreachable) and producttimeoutMs: 1500:waitForResultsSettle is exactly the timeout plus the scroll pass. On a healthy idle laptop the content happened to be complete by then; on a page that keeps drifting under load, or whose monitor history truncates and answers
-1, it would not be.The fix
awaitContractreturns two verdicts, because they are two different things:satisfied— every clause held. This is what the report carries and what the plugin'srender_readinessverdict is built from. Unchanged.stopped— the loop left on the held-and-quiet exit. This is what the settle acts on. Set only there; the deadline, the rot valve and a page that went away all fall through to the ordinary settle, exactly as an unsatisfied contract does.ReadinessResultgainsstopped(in-process). The wire shape is unchanged — a satisfied-but-never-quiet render reportssatisfied: truewithwaitedMs ≈ timeoutMs, which is honest but not yet a distinct series; surfacing "held but fell back" as its own metric is a plugin follow-up, noted rather than half-done here.Test
test/readiness.test.ts: a fixture that satisfies its contract at once and then appends 20 elements every 40 ms for as long as the page lives, with awaitForrule as the witness that the fallback settle ran. Asserts the gate ran,satisfiedis true,stoppedis false, and the contract waited out its timeout.Browser suite 295/295, root
lintandformat:checkclean. Version reservations checked: open #178 and #188 sit at 1.29.0,mainis 1.31.0, so 1.31.1 is free.Deploy consequence
This is the browser the fleet should arm on. render-service
kohlspins 1.31.0 withonSatisfied: 'quiet'and has not been deployed yet; the companion config change is HarperFast/render-service (reveal-state clause), and its dep bump follows this release. Read off the four prod nodes on 2026-09-21: the plugin is still 0.72.0 everywhere (norender_readiness, nothing staged), so the readiness metric records nothing until kohls-pr's 0.80.0 train is deployed — arm after that, or the fleet'ssatisfied_msdistribution is invisible.🤖 Generated with Claude Code