diff --git a/scripts/recovery_e2e.py b/scripts/recovery_e2e.py index 6a73f248..af4b4ad7 100644 --- a/scripts/recovery_e2e.py +++ b/scripts/recovery_e2e.py @@ -1771,7 +1771,7 @@ def _poll_until(pred: Any, timeout: float, interval: float = 0.1) -> bool: def _send_in_page(cdp: CDP, message: str) -> None: """POST /send from inside the page via the pane's own authFetch (cookie auth, node-proxy base) — the one shared shape for scenarios that drive a - turn mid-flight (E1/E4). A raw POST emits no live user row, so the sent + turn mid-flight (E1/E4/G3). A raw POST emits no live user row, so the sent turn appears only via the next /history render.""" cdp.evaluate( "window.authFetch('/v1/api/workstreams/' + " diff --git a/tests/test_app_js.py b/tests/test_app_js.py index 27a4f097..7aa656bc 100644 --- a/tests/test_app_js.py +++ b/tests/test_app_js.py @@ -2190,14 +2190,18 @@ def test_coord_truncated_resync_is_full_fresh_connect_with_churn_limit() -> None assert "console.warn(" in rec.group(1) # (9) repair-intent supersession lives in refetchHistory's success path # — record + deferred latch + pending timer, all below the !hist guard - # — and clear_ui carries no path-local cancel. + # — and clear_ui carries no path-local cancel of the TRUNCATED repair + # intent. Pin the specific timer, not all clearTimeout: #894's + # staleRetryTimer re-arm cancel in clear_ui is the staleness latch's + # own machinery, deliberately armed there (see the coord latch-contract + # test), not a truncated-repair cancel. assert "pendingTruncatedResync = false;" in fn assert "clearTimeout(truncatedResyncTimer)" in fn assert guard < fn.index("pendingTruncatedResync = false;") assert guard < fn.index("clearTimeout(truncatedResyncTimer)") cu = re.search(r'case "clear_ui": \{(.*?)\n \}', body, re.S) assert cu is not None, "clear_ui case not found" - assert "clearTimeout" not in cu.group(1) + assert "clearTimeout(truncatedResyncTimer)" not in cu.group(1) def test_coord_detects_server_restart_by_backwards_event_id() -> None: diff --git a/tests/test_coordinator_page.py b/tests/test_coordinator_page.py index 61fa661c..8ba4e951 100644 --- a/tests/test_coordinator_page.py +++ b/tests/test_coordinator_page.py @@ -664,12 +664,23 @@ def test_coordinator_history_stale_latch_contract(): "reconnect — a reload's fresh reconnect draws the synthetic " "state_change:idle back into this trigger (zero-backoff storm)." ) + assert "!refetchesInFlight" in backstop_arm, ( + "the backstop must yield to an in-flight refetch — without the " + "guard it stomps a same-snapshot fetch with a double render " + "(mirrors interactive's !_replayQueue pin)." + ) - # 5. Bounded retry: exactly one arm site. + # 5. Bounded retry: exactly one arm site, and its fire guard yields to + # an in-flight fetch. assert body.count("staleRetryTimer = setTimeout") == 1, ( "the stale retry must be armed in exactly one place (clear_ui " ".then) — bounded by construction." ) + retry_arm = body.index("staleRetryTimer = setTimeout") + assert "!refetchesInFlight" in body[retry_arm : retry_arm + 700], ( + "the retry's fire guard must yield to an in-flight refetch " + "(mirrors interactive's !_replayQueue pin)." + ) # 6. Teardown: terminal cancel in destroy(); NOT in closeStreamTransport. destroy_slice = body[body.index("function destroy()") :]