ouroboros/tests/test_review_session_poller.py
Ouroboros 04b1de9c95 v7next F2.3a: domain D06 review mechanics - six-monolith split from tip bytes; state one-cut with custody leaf; session-route and substrate test giants split; dispatch dedup
Module side. Eleven owner leaves cut from tip bytes, every span
transplant-tool proof-green against git show HEAD:<monolith>
(ast=tokens=byte-roundtrip on every symbol, leaf_invariants=[], exit 0):

- review_state.py ONE CUT (owner decision 5.3=B, GIANT 2172->777):
  review_state_records (44 rows, _rs handle), review_state_model
  (AdvisoryReviewState, _rs), and the NEW review_state_custody leaf - nine
  unrowed post-cutoff symbols of the adaptive-timeout/custody train
  (checkpoint_pending_review_invocation family), recorded in
  LEDGER_CORRECTIONS as unrowed F5 adoption rows. The authority-shape
  deserializers stay with the parent STORE.
- review_substrate.py (1600->815): review_records (projection-only, off the
  LEAVES table), review_verdict and review_projection on the _sub handle.
  Upstream re-homes honored, not dragged back: reviewer_slots stays in
  reviewer_slot_config, _render_prompt in review_execution, slot_id_for_row
  in review_dispatch - all pinned by the re-derived extraction suite.
- tools/review_helpers.py (1575->764): review_prompt_text (27 rows) +
  review_file_pack (25 rows) on the _rh handle.
- tools/scope_review.py (1597->963): scope_review_pack (19/20 rows, _sr);
  _load_canonical_context_docs stays a facade def (f-string read of
  load_governance_doc, which tests rebind on the parent - D10 precedent).
  The budget leaf is the F2.3b re-derive (#383), untouched here.
- review_evidence.py (1559->886): review_evidence_sections (25 rows, _ev);
  the two capability-delta rows are superseded by upstream's
  delegate_evidence home and not replayed.
- tools/review.py (1550->1269): review_multi_model (7 rows, _rev);
  _parse_model_response superseded (tools/review_response is the home),
  the two review-model timeout rows retired with the adaptive-timeout
  contract.

Declared sets are the tool-derived exact read sets (maximal-declared
policy); the few f-string/import-time reads the gate refuses stay
import-bound to their canonical owners and are named in each leaf docstring
(none of those names is monkeypatched on a parent anywhere in tests/).
Facades = tip parent - moved spans + EOF re-export block + noqa discipline;
dead stdlib imports trimmed. Drift-probe first per leaf: 182 rowed oracle
spans probed against tip bytes (155 byte-true, 27 drifted); all bodies
emitted from tip bytes, no oracle semantics replayed over drift.

Path-keyed mirrors: review_context_atlas._REVIEW_STACK_PATHS and
run_external_review._REVIEW_SUBSTRATE_PATHS extended additively with the
new leaves beside their parents (D10 closure precedent); domains.toml
gains the eleven D06 leaf rows and clears the resolved split_pending
entries; the domain quotient report regenerated (no manifest drift).

Test side. The two D06 test giants re-cut as the reference theme split from
tip bytes, lossless:
- test_review_agent_session_route.py (3399, GIANT) -> shared fixtures
  (_review_session_route_shared) + delivery/poller/scope_wiring siblings +
  a 1218-line remainder (102 == 102 test names; thirteen post-cutoff tests
  placed with the sibling that owns their helpers; three reference-only
  tests not replayed, recorded).
- test_review_substrate_v2.py (2986, GIANT) -> shared FakeLLM +
  extraction/acceptance/actor_truth/prompts siblings + a NEW
  test_review_substrate_custody.py sibling holding the eighteen post-cutoff
  custody-train tests (71 == 71 test names). The falsified ledger row
  (_render_prompt -> substrate) is re-derived: the prompts suite imports it
  from review_execution; LEDGER_CORRECTIONS carries the correction.
Both giants leave GIANT_PATHS; the two band re-entries carry rationales.

Dedup (owner decision 5.2=A), disclosed as a test deletion: ten
AST-identical tests + seven byte-identical orphan helpers removed from
test_review_cycles_dispatch.py; the owner is
test_review_cycles_skill_dispatch.py (D14 family). -510 double-executed
lines; the dispatch file stays the commit-gate paid-accounting suite and
leaves the ratchet band.

Dead-patch class re-pointed per the oracle adaptations: six sites patching
leaf-internal names on the facade (test_scope_review
TestRunScopeReviewFailClosed x4, test_review_convergence_rule fixture)
retargeted to scope_review_pack; every other historical monkeypatch target
stays live on the parents through the call-time handles. New identity
suites: five re-derived extraction contracts + re-derived
test_review_owner_facades (superseded/retired rows dropped with reasons);
ten LEAVES rows added to test_module_handle_extraction.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
(cherry picked from commit 05ec5fd1173c9895c7b0ac11124979eeeb44d9ef)
2026-08-31 10:38:37 +00:00

537 lines
22 KiB
Python

"""The session poller: early termination and honest cancel outcomes.
Split by theme out of ``tests/test_review_agent_session_route.py``. This module
owns the poller: a waiting-on-user session ends early and typed, the cancel
read is honest about what it proved on both branches, a discovered success is
never lost to a re-read blip, and confirmed natural terminals are attributed to
the run rather than the host.
"""
from types import SimpleNamespace
import pytest
from tests._review_session_route_shared import _owned_gateway_uses_each_test_transport as __owned_gateway_uses_each_test_transport
# The autouse transport fixture is requested by pytest, not by name, so it is re-bound
# through a module attribute exactly as in the sibling suites: leaving it behind would
# have silently let this suite reach the real owned gateway.
_owned_gateway_uses_each_test_transport = __owned_gateway_uses_each_test_transport
# ---------------------------------------------------------------------------
# F18: the poller terminates a slot EARLY on a session waiting on user
# ---------------------------------------------------------------------------
def test_poller_terminates_a_waiting_on_user_session_early_and_typed(tmp_path):
"""F18 (sol #6 minimal form + grok): a delegated review session that parks
on an interactive question cannot be answered host-side (review slots are
non-interactive; answering support is a future issue) — waiting out the
engine timeout burns the whole slot budget in silence. The poller cancels
the run through the verified-cancel path under its OWN typed reason and
raises a typed failure naming the pending question."""
import time as _time
from ouroboros.review_execution import (
ReviewSessionWaitingOnUser,
_poll_session_terminal,
)
cancelled = {}
class _WaitingGateway:
def get_run(self, run_id, *, timeout_sec=None):
return {
"lastSeq": 3,
"pendingInteractions": [{
"interactionId": "int-9",
"questions": [{"id": "q1",
"question": "Which schema version applies?",
"options": [], "multi_select": False}],
}],
"summary": {"state": "running", "waitingOnUser": True},
}
class _CustodyStub:
@staticmethod
def is_terminal(detail):
return False
@staticmethod
def summary_of(detail):
return detail.get("summary") or {}
@staticmethod
def cancel_and_verify(drive, gateway, entry, reason):
cancelled["reason"] = reason
return {"outcome": "confirmed"}
started = _time.monotonic()
with pytest.raises(ReviewSessionWaitingOnUser) as excinfo:
_poll_session_terminal(_WaitingGateway(), _CustodyStub(), tmp_path,
SimpleNamespace(run_id="run-w"), "run-w", 600.0)
# EARLY: no slot-long burn — the first poll already decided.
assert _time.monotonic() - started < 30.0
# The run was cancelled under the typed host-side reason (no
# cancel-vs-decline ambiguity), and the failure names the question.
assert cancelled["reason"] == "review_session_waiting_on_user"
text = str(excinfo.value)
assert "int-9" in text
assert "Which schema version applies?" in text
assert "host-cancelled" in text
def _parked_detail(timeout_at):
row = {
"interactionId": "int-9",
"questions": [{"id": "q1", "question": "Which schema version applies?",
"options": [], "multi_select": False}],
}
if timeout_at is not None:
row["timeoutAt"] = timeout_at
return {
"lastSeq": 3,
"pendingInteractions": [row],
"summary": {"state": "running", "waitingOnUser": True},
}
class _PollCustodyStub:
def __init__(self):
self.cancelled = {}
@staticmethod
def is_terminal(detail):
return str((detail.get("summary") or {}).get("state") or "") == "succeeded"
@staticmethod
def summary_of(detail):
return detail.get("summary") or {}
def cancel_and_verify(self, drive, gateway, entry, reason):
self.cancelled["reason"] = reason
return {"outcome": "confirmed"}
def test_poller_keeps_polling_when_the_engine_timeout_lands_inside_the_slot(
tmp_path, monkeypatch):
"""R2-2 (regressions HIGH — the genuine F18 regression): a parked question
whose OWN timeout_at provably lands inside the slot's remaining budget is
the recoverable pre-F18 case — the engine benign-declines it and the
session resumes. The poller must KEEP POLLING on the slot's own clock (not
an owner host-wait), never cancel a session the engine is about to
resume."""
from datetime import datetime, timedelta, timezone
from ouroboros import review_execution as rx
monkeypatch.setattr(rx, "_SESSION_POLL_SEC", 0.01)
soon = (datetime.now(timezone.utc) + timedelta(seconds=30)).isoformat()
calls = {"n": 0}
class _RecoveringGateway:
def get_run(self, run_id, *, timeout_sec=None):
calls["n"] += 1
if calls["n"] < 3:
return _parked_detail(soon)
return {"lastSeq": 4, "summary": {"state": "succeeded"}}
custody = _PollCustodyStub()
detail = rx._poll_session_terminal(
_RecoveringGateway(), custody, tmp_path,
SimpleNamespace(run_id="run-w"), "run-w", 600.0)
assert detail["summary"]["state"] == "succeeded"
assert calls["n"] >= 3
assert custody.cancelled == {}, "a recoverable park must not be cancelled"
@pytest.mark.parametrize("timeout_at", [
None, # absent: no engine expiry exists
"not-a-timestamp", # unparseable: no PROVEN expiry
"at_deadline", # lands at/after the slot deadline
"far_future", # far beyond the slot
])
def test_poller_still_terminates_when_no_expiry_lands_inside_the_slot(
tmp_path, timeout_at):
"""R2-2, the four negative shapes: without a PROVEN engine expiry inside
the slot budget, the park is still the F18 slot-long silent burn — cancel
plus the typed raise, exactly as before."""
from datetime import datetime, timedelta, timezone
from ouroboros.review_execution import (
ReviewSessionWaitingOnUser,
_poll_session_terminal,
)
slot_seconds = 600.0
if timeout_at == "at_deadline":
timeout_at = (datetime.now(timezone.utc)
+ timedelta(seconds=slot_seconds + 1)).isoformat()
elif timeout_at == "far_future":
timeout_at = (datetime.now(timezone.utc)
+ timedelta(days=1)).isoformat()
class _ParkedGateway:
def get_run(self, run_id, *, timeout_sec=None):
return _parked_detail(timeout_at)
custody = _PollCustodyStub()
with pytest.raises(ReviewSessionWaitingOnUser):
_poll_session_terminal(_ParkedGateway(), custody, tmp_path,
SimpleNamespace(run_id="run-w"), "run-w", slot_seconds)
assert custody.cancelled["reason"] == "review_session_waiting_on_user"
# ---------------------------------------------------------------------------
# BR1-1: the poller is HONEST about what the cancel proved, on both branches
# ---------------------------------------------------------------------------
class _OutcomeCustodyStub:
"""cancel_and_verify scripted to one typed outcome (or an exception)."""
def __init__(self, outcome, state="running", raises=False):
self.outcome, self.state, self.raises = outcome, state, raises
self.cancels = []
@staticmethod
def is_terminal(detail):
return str((detail.get("summary") or {}).get("state") or "") in (
"succeeded", "failed", "cancelled", "interrupted")
@staticmethod
def summary_of(detail):
return detail.get("summary") or {}
def cancel_and_verify(self, drive, gateway, entry, reason):
self.cancels.append(reason)
if self.raises:
raise RuntimeError("daemon died mid-cancel")
return {"outcome": self.outcome, "state": self.state,
"accepted": self.outcome in ("confirmed", "requested"),
"control_status": "", "fault_reason": "", "detail": ""}
class _RunningGateway:
"""Non-terminal, no pending questions — drives the timeout branch."""
def __init__(self, waiting=False):
self.waiting = waiting
def get_run(self, run_id, *, timeout_sec=None):
if self.waiting:
return _parked_detail(None)
return {"lastSeq": 1, "summary": {"state": "running"}}
class _SucceededAfterCancelGateway(_RunningGateway):
"""The natural-success race: the verify read finds the run SUCCEEDED, so
the re-read after cancel returns the natural terminal."""
def __init__(self, waiting=False):
super().__init__(waiting)
self.cancel_seen = False
def get_run(self, run_id, *, timeout_sec=None):
if self.cancel_seen:
return {"lastSeq": 9, "summary": {"state": "succeeded"},
"primaryOutput": {"text": "[]"}}
return super().get_run(run_id, timeout_sec=timeout_sec)
_CANCEL_OUTCOME_CASES = [
("confirmed", "host-cancelled"),
("requested", "may still be live"),
("failed", "may still be live"),
("containment_fault_run_may_still_be_live", "may still be live"),
]
@pytest.mark.parametrize("outcome,expected", _CANCEL_OUTCOME_CASES)
def test_waiting_on_user_raise_carries_the_honest_cancel_outcome(
tmp_path, outcome, expected):
"""BR1-1(b): "host-cancelled" is claimed ONLY for a `confirmed` verified
receipt; requested/failed/containment-fault raises say the cancel was
requested-but-unverified and the run MAY STILL BE LIVE — same typed
exception class, distinct reason text."""
from ouroboros.review_execution import (
ReviewSessionWaitingOnUser,
_poll_session_terminal,
)
stub = _OutcomeCustodyStub(outcome)
with pytest.raises(ReviewSessionWaitingOnUser) as excinfo:
_poll_session_terminal(_RunningGateway(waiting=True), stub, tmp_path,
SimpleNamespace(run_id="run-h"), "run-h", 600.0)
text = str(excinfo.value)
assert expected in text
if outcome != "confirmed":
assert "host-cancelled" not in text
assert outcome in text
assert stub.cancels == ["review_session_waiting_on_user"]
@pytest.mark.parametrize("outcome,expected", _CANCEL_OUTCOME_CASES)
def test_slot_timeout_raise_carries_the_honest_cancel_outcome(
tmp_path, monkeypatch, outcome, expected):
"""BR1-1(c): the same outcome-honesty on the slot-timeout cancel — a
TimeoutError whose text claims "host-cancelled" only when the receipt is
verified."""
from ouroboros import review_execution as rx
monkeypatch.setattr(rx, "_SESSION_POLL_SEC", 0.01)
stub = _OutcomeCustodyStub(outcome)
with pytest.raises(TimeoutError) as excinfo:
rx._poll_session_terminal(_RunningGateway(), stub, tmp_path,
SimpleNamespace(run_id="run-t"), "run-t", 1.0)
text = str(excinfo.value)
assert "exceeded the slot budget" in text
assert expected in text
if outcome != "confirmed":
assert "host-cancelled" not in text
assert outcome in text
assert stub.cancels == ["review_slot_timeout"]
@pytest.mark.parametrize("waiting,slot_seconds", [
(True, 600.0), # waiting-on-user branch
(False, 1.0), # slot-timeout branch
])
def test_natural_success_discovered_by_the_cancel_read_wins_on_both_branches(
tmp_path, monkeypatch, waiting, slot_seconds):
"""BR1-1(a) COMPLETION WINS: when cancel/verify reports the run reached a
natural SUCCESS terminal (settled state=succeeded), the poller consumes
that terminal as the slot's ordinary result instead of raising."""
from ouroboros import review_execution as rx
monkeypatch.setattr(rx, "_SESSION_POLL_SEC", 0.01)
gateway = _SucceededAfterCancelGateway(waiting=waiting)
stub = _OutcomeCustodyStub("confirmed", state="succeeded")
orig = stub.cancel_and_verify
def _cancel(drive, gw, entry, reason):
gateway.cancel_seen = True
return orig(drive, gw, entry, reason)
stub.cancel_and_verify = _cancel
detail = rx._poll_session_terminal(
gateway, stub, tmp_path, SimpleNamespace(run_id="run-s"), "run-s",
slot_seconds)
assert detail["summary"]["state"] == "succeeded"
assert detail["primaryOutput"]["text"] == "[]"
@pytest.mark.parametrize("waiting", [True, False])
def test_a_raising_cancel_is_reported_unverified_not_host_cancelled(
tmp_path, monkeypatch, waiting):
"""BR1-1 exception shape: a cancel/verify that RAISES is an unverified
attempt — the typed slot failure still fires (same exception class per
branch) and its text says the run may still be live, never
"host-cancelled"."""
from ouroboros import review_execution as rx
monkeypatch.setattr(rx, "_SESSION_POLL_SEC", 0.01)
stub = _OutcomeCustodyStub("confirmed", raises=True)
exc_type = rx.ReviewSessionWaitingOnUser if waiting else TimeoutError
with pytest.raises(exc_type) as excinfo:
rx._poll_session_terminal(
_RunningGateway(waiting=waiting), stub, tmp_path,
SimpleNamespace(run_id="run-x"), "run-x",
600.0 if waiting else 1.0)
text = str(excinfo.value)
assert "may still be live" in text
assert "host-cancelled" not in text
# ---------------------------------------------------------------------------
# BR2-1: a discovered success is NEVER lost to a re-read failure
# BR2-2: a confirmed natural terminal is attributed to the run, not the host
# ---------------------------------------------------------------------------
class _CarryingCustodyStub(_OutcomeCustodyStub):
"""cancel_and_verify that also carries the verify read's own detail
(the additive `terminal_detail` key of `_cancel_result`, BR2-1)."""
def __init__(self, outcome, state="running", carried=None):
super().__init__(outcome, state)
self.carried = carried
def cancel_and_verify(self, drive, gateway, entry, reason):
result = super().cancel_and_verify(drive, gateway, entry, reason)
if self.carried is not None:
result["terminal_detail"] = self.carried
return result
class _BlippingGateway(_RunningGateway):
"""After the cancel, `fail_reads` re-reads RAISE (the transport blip),
then the settled success detail is served."""
def __init__(self, waiting=False, fail_reads=99):
super().__init__(waiting)
self.cancel_seen = False
self.fail_reads = fail_reads
self.reads_after_cancel = 0
def get_run(self, run_id, *, timeout_sec=None):
if self.cancel_seen:
self.reads_after_cancel += 1
if self.reads_after_cancel <= self.fail_reads:
raise RuntimeError("transport blip")
return {"lastSeq": 9, "summary": {"state": "succeeded"},
"primaryOutput": {"text": "[]"}}
return super().get_run(run_id, timeout_sec=timeout_sec)
def _arm_cancel(gateway, stub):
"""Flip the gateway into its post-cancel behaviour when the stub cancels."""
orig = stub.cancel_and_verify
def _cancel(drive, gw, entry, reason):
gateway.cancel_seen = True
return orig(drive, gw, entry, reason)
stub.cancel_and_verify = _cancel
@pytest.mark.parametrize("waiting", [True, False])
def test_the_carried_terminal_detail_wins_with_no_second_fetch(
tmp_path, monkeypatch, waiting):
"""BR2-1: when cancel_and_verify carried the verify read's own succeeded
detail, the poller consumes it AS the slot result — even though every
re-read would raise — and issues no post-cancel fetch at all (the extra
get_run round-trip of the success race is gone)."""
from ouroboros import review_execution as rx
monkeypatch.setattr(rx, "_SESSION_POLL_SEC", 0.01)
carried = {"lastSeq": 9, "summary": {"state": "succeeded"},
"primaryOutput": {"text": "[]"}}
gateway = _BlippingGateway(waiting=waiting, fail_reads=99)
stub = _CarryingCustodyStub("confirmed", state="succeeded", carried=carried)
_arm_cancel(gateway, stub)
detail = rx._poll_session_terminal(
gateway, stub, tmp_path, SimpleNamespace(run_id="run-c"), "run-c",
600.0 if waiting else 1.0)
assert detail is carried
assert gateway.reads_after_cancel == 0, "no second fetch after a carried detail"
def test_an_uncarried_success_survives_one_re_read_blip(tmp_path, monkeypatch):
"""BR2-1 bounded retry: without a carried detail (older custody shape),
a single re-read failure does not lose the success — the one retry
fetches the settled terminal."""
from ouroboros import review_execution as rx
monkeypatch.setattr(rx, "_SESSION_POLL_SEC", 0.01)
gateway = _BlippingGateway(fail_reads=1)
stub = _OutcomeCustodyStub("confirmed", state="succeeded")
_arm_cancel(gateway, stub)
detail = rx._poll_session_terminal(
gateway, stub, tmp_path, SimpleNamespace(run_id="run-r"), "run-r", 1.0)
assert detail["summary"]["state"] == "succeeded"
assert gateway.reads_after_cancel == 2
@pytest.mark.parametrize("waiting", [True, False])
def test_an_unreadable_settled_success_raises_typed_never_may_still_be_live(
tmp_path, monkeypatch, waiting):
"""BR2-1 last resort: the state is KNOWN (succeeded, settled) — when the
detail stays unreadable after the bounded retry, the typed failure says
exactly that and names the recovery surfaces; it never falls through to
the "may still be live" honesty clause, and the read attempts stay
bounded (one retry, never a loop)."""
from ouroboros import review_execution as rx
monkeypatch.setattr(rx, "_SESSION_POLL_SEC", 0.01)
gateway = _BlippingGateway(waiting=waiting, fail_reads=99)
stub = _OutcomeCustodyStub("confirmed", state="succeeded")
_arm_cancel(gateway, stub)
with pytest.raises(rx.ReviewSessionSucceededResultUnavailable) as excinfo:
rx._poll_session_terminal(
gateway, stub, tmp_path, SimpleNamespace(run_id="run-u"), "run-u",
600.0 if waiting else 1.0)
text = str(excinfo.value)
assert "SUCCEEDED" in text and "settled" in text
assert "delegate_wait" in text, "the recovery surface is named"
assert "may still be live" not in text
assert "host-cancelled" not in text
assert gateway.reads_after_cancel == 2, "one bounded retry, never a loop"
assert excinfo.value.delegated_run_started is True
assert excinfo.value.delegated_run_id == "run-u"
@pytest.mark.parametrize("state,must,must_not", [
("failed", "its own terminal state 'failed'", "host-cancelled"),
("interrupted", "its own terminal state 'interrupted'", "host-cancelled"),
("cancelled", "host-cancelled with a verified terminal receipt", "its own terminal"),
("settled", "host-cancelled with a verified terminal receipt", "its own terminal"),
("absent", "host-cancelled with a verified terminal receipt", "its own terminal"),
("", "host-cancelled with a verified terminal receipt", "its own terminal"),
])
def test_confirmed_attribution_follows_the_verified_state(state, must, must_not):
"""BR2-2, every wording branch: a `confirmed` whose verified state is the
run's OWN non-success terminal (failed/interrupted) is attributed to the
run; 'cancelled' — and the receipt-is-the-cancel states ''/settled/absent —
keep the host-cancelled verified-receipt wording; nothing here ever says
"may still be live"."""
from ouroboros.review_execution import _cancel_honesty_clause
text = _cancel_honesty_clause("confirmed", state)
assert must in text, text
assert must_not not in text, text
assert "may still be live" not in text
# The unverified wording is unchanged by the state parameter.
assert "may still be live" in _cancel_honesty_clause("requested", state)
@pytest.mark.parametrize("waiting", [True, False])
def test_a_confirmed_natural_terminal_is_attributed_to_the_run_not_the_host(
tmp_path, monkeypatch, waiting):
"""BR2-2 through the poller: a run that had ALREADY failed on its own when
the verify read arrived raises the branch's typed failure wording the
natural terminal — never claiming the host's cancel stopped it."""
from ouroboros import review_execution as rx
monkeypatch.setattr(rx, "_SESSION_POLL_SEC", 0.01)
stub = _OutcomeCustodyStub("confirmed", state="failed")
exc_type = rx.ReviewSessionWaitingOnUser if waiting else TimeoutError
with pytest.raises(exc_type) as excinfo:
rx._poll_session_terminal(
_RunningGateway(waiting=waiting), stub, tmp_path,
SimpleNamespace(run_id="run-f"), "run-f",
600.0 if waiting else 1.0)
text = str(excinfo.value)
assert "its own terminal state 'failed'" in text
assert "host-cancelled" not in text
assert "may still be live" not in text
def test_clock_crossing_between_timeout_checks_still_cancels_the_running_session(
tmp_path, monkeypatch):
"""The second clock read may cross the deadline before sleep is entered."""
from ouroboros import review_execution as rx
ticks = iter([0.0, 0.0, 0.5, 1.1, 1.2])
# Replace the module reference, not attributes on the process-global
# ``time`` module: pytest itself calls monotonic on Windows and would
# exhaust the finite tick iterator (same scoping precedent as
# tests/test_external_unmetered_dispatch.py).
monkeypatch.setattr(
rx,
"time",
SimpleNamespace(monotonic=lambda: next(ticks), sleep=lambda _seconds: None),
)
monkeypatch.setattr(
rx,
"_poll_detail",
lambda *_args, **_kwargs: {"lastSeq": 1, "summary": {"state": "running"}},
)
stub = _OutcomeCustodyStub("confirmed")
with pytest.raises(TimeoutError, match="exceeded the slot budget"):
rx._poll_session_terminal(
_RunningGateway(), stub, tmp_path,
SimpleNamespace(run_id="run-cross"), "run-cross", 1.0,
)
assert stub.cancels == ["review_slot_timeout"]