Skip to content

docs(plan): inject a request clock so the idle-timeout tests stop waiting on wall time (slice C3 of #235) - #289

Merged
ViperJuice merged 2 commits into
mainfrom
plan/235-c3-clock
Sep 23, 2026
Merged

ViperJuice merged 2 commits into
mainfrom
plan/235-c3-clock

Conversation

@ViperJuice

@ViperJuice ViperJuice commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Detailed plan for slice C3 of see #235: the last open slice of the test-hermeticity umbrella. Plan only; src/ and tests/ unchanged.

Plan: .consiliency/plans/detailed-235-c3-clock-injection-20260923-1229.md

Design in brief

  • ClientManager(clock=...) is injected through the constructor; the default is time.time. Seven of the 18 time.time() call sites in manager.py join the seam: every site that stamps or compares a request's started_at / last_heartbeat / last_activity_at. The other 11 are reported timestamps or IDs that are never compared with a request stamp, and they stay on wall time.
  • The idle-poll slice (min(idle_timeout_s, 1.0)) becomes a named constant tests can shrink. A fake clock that counts reads lets each test step time exactly once per idle check, so no test waits in real time and none asserts an upper bound on wall-clock time.
  • Monotonic is out of scope, as a named follow-up. These stamps are epoch values used outside the manager (cli.py:1478/1549, handlers.py:6335), so a monotonic default would make gateway.list_pending show 1970.

Verified independently by the integrator

Applied the plan's diff and test bodies to a scratch worktree off current main, adding the three test imports the plan lists in its changes table:

  • tests/test_client_manager.py: 253 passed; ruff check, ruff format --check and mypy src/ clean.
  • The three heartbeat tests dropped from 0.50 s / 0.41 s / 0.20 s of real waiting to 0.01 s each.
  • Mutation: moving the idle-timeout comparison back to time.time() made exactly the three named tests fail. Restored byte-identical.
  • 30 of 30 flake runs passed.

🤖 Generated with Claude Code

https://claude.ai/code/session_013G8iK4noj1iNbSTqJVFEX6


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

ViperJuice and others added 2 commits September 23, 2026 12:46
Detailed, spike-measured plan for the last open slice of the test-hermeticity
umbrella #235: a constructor-injected request clock on
ClientManager (7 of 18 time.time() sites join the seam; the 11 reported
timestamps stay on wall time), an IDLE_POLL_SLICE_S constant so the idle
re-check cadence is patchable, and the idle-timeout tests rewritten to drive a
fake clock one idle check at a time. Monotonic time is a named follow-up, not
part of this slice, because gateway.list_pending and pmcp status render the
stamps as epoch seconds.

Measured this session on a throwaway spike (reverted; src/ and tests/ are
byte-identical to HEAD in this commit): 10 mutants red by name, 1 slice
mutant detected by timing only; 40/40 repeated runs, 0.045 s mean call time
vs a 1.11 s baseline; whole suite 4068 passed. Plan-only commit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013G8iK4noj1iNbSTqJVFEX6
… not hang it

Board review of PR #289 found one blocking test-liveness gap
in the C3 plan for #235: when the idle check stops reading the
clock (new mutant M12, `now = pending.started_at`), the three driver-based
tests hung to pytest-timeout (700 s each) instead of failing by name — past
the CI job's 25-minute limit, so the job would be cancelled with no red test.
Each driver now wraps its loop in `try/except BaseException` and fails every
in-flight future with its own exception (`_fail_pending`), so the main await
ends by name in ~5 s. Measured: M12 before the fix 3 × `Failed: Timeout` (20 s
cap); after it `3 failed, 7 passed in 15.14s`, each `condition not met within
5.0s`. M1–M11 re-run on the revised spike with identical outcomes; 40/40
repeated runs again (0.040 s mean call time); file 253 passed; ruff, format
and mypy clean.

Also: the PendingRequest comment no longer claims the stamps are compared
only with the request clock (handlers/cli subtract time.time() today; that is
the monotonic follow-up), the board's `ClientManager.now()` suggestion is
recorded as a declined non-goal with reasons, and Verification gains the
`uv sync -p 3.10 --all-extras` prerequisite and the seat's Python 3.14.7
confirmation. Plan-only commit; src/ and tests/ are byte-identical to HEAD.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013G8iK4noj1iNbSTqJVFEX6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant