Skip to content

The Drift Log · Determinism and model independence

Seeding Clocks and Entropy in Production Test Suites

3 September 2026 · 3 min read · 769 words · established

Two identical crystal prisms casting exactly the same refraction

Eliminate flaky test runs by treating physical clocks and random number generators as explicit, seedable inputs rather than ambient global state.

The build passed on your machine at 4:15 PM on Friday. It failed in CI at 12:02 AM on Sunday. When you re-ran the job on Monday morning without changing a single line of code, it passed again.

The team labelled it one of their occasional flaky tests and moved on.

Tests do not fail at midnight because the hardware is capricious. They fail because your application logic touched system time across a day, month, or leap-year boundary. They fail because a test fixture generated a UUID with unseeded entropy, hit an edge-case string length or character set once in ten thousand runs, and evaporated before you could attach a debugger.

If your code reaches out to the host environment for time or randomness, your test suite is not a closed verification system. It is an open loop dependent on environmental drift.

The ambient clock is hidden global state

Calling system time directly inside domain logic couples business rules to the physical wall clock of the execution runner. Token expiration, cache invalidation, rate-limiting windows, and scheduling logic all rot under ambient time calls.

When a test asserts that an authentication token expires after sixty seconds, sleeping the process for sixty-one seconds is both slow and brittle. Mocking the system clock globally via runtime monkey-patching is equally hazardous in concurrent test environments, where asynchronous tasks step on each other’s patched globals.

The remedy is mechanical: treat time as an explicit input.

// Brittle: reads ambient environment
function isSessionValid(session: Session): boolean {
  return Date.now() < session.expiresAt;
}

// Deterministic: time is an injected parameter
function isSessionValid(session: Session, now: number): boolean {
  return now < session.expiresAt;
}

When time is an explicit parameter or an injected interface, time travel in unit and integration tests costs zero CPU cycles. You can assert behavior across leap seconds, year-end rollovers, and multi-day timeouts instantaneously and deterministically.

Unseeded entropy produces unreproducible failures

Randomness in test suites typically enters through two paths: generative property testing and random fixture generators. Both are valuable for finding edge cases. Both become liabilities when the source of randomness is ambient.

If a test fails under crypto.randomUUID() or Math.random(), you cannot replay the failure. You know only that a bug exists somewhere in the state space; you do not have the coordinates to locate it.

Building reliable, deterministic software requires seeded randomness. Every test run that relies on random inputs must accept a seed from the execution harness. If the seed is not explicitly provided, the harness generates one, logs it to stdout at the start of the run, and uses it to initialise a pseudo-random number generator (PRNG).

## Running with an explicit seed to reproduce a CI failure
npm test -- --seed=0x7F4A9C12

When a property test fails in CI, the logged seed allows any developer to reproduce the identical failure on their local workstation on the first attempt. The state space collapses from infinite possibilities down to one reproducible execution trace.

Where time virtualization hits reality

Eliminating non-determinism inside domain logic does not solve every source of test variance. Concurrency and operating system scheduling remain genuinely difficult.

You can freeze domain clocks and seed your PRNGs, but you cannot easily freeze the operating system thread scheduler, garbage collection pauses, or asynchronous I/O completion order without full hypervisor-level record-and-replay systems.

The practical boundary is architectural:

  1. Pure logic and state transitions must be 100% deterministic. Clocks, entropy, and identity generation must be injected.
  2. I/O boundaries and integration points must rely on explicit synchronisation primitives (such as completion barriers or event queues) rather than arbitrary sleep() durations designed to "wait long enough" for an operation to complete.

Separating your deterministic core logic from ambient system inputs is the same discipline required for reproducible builds and reliable verification harnesses. If an artifact's behavior depends on the exact millisecond it was evaluated, you do not own the system's behavior—the environment does.

Auditing the suite on Monday

You do not need to rewrite your application architecture this week to make progress. Start with three concrete rules in your codebase:

  1. Ban ambient clock access in domain directories. Add a linter rule that forbids Date.now(), new Date(), time.Now(), or platform equivalents outside of explicit system-adapter boundaries.
  2. Log the random seed on every test run. Configure your test runner to emit the root seed in the header of the test output. If a test runner does not support seeded execution, replace its internal random source with a lightweight seeded PRNG.
  3. Eliminate sleep calls in assertions. Search your test files for sleep(), delay(), or setTimeout(). Replace each with an explicit condition poll or an event-driven barrier with a strict, predictable timeout.

Flaky tests are not an inevitable tax of running automated software verification. They are defects in how your system manages its dependencies on time and entropy. Close the inputs, and the flakiness disappears.

This post supports the longer argument in Why Model Independence Is an Engineering Posture.

Keep reading

Next in the log

The Strategic Master Library · written and reviewed under the house's own epistemic rules: nothing claimed that we cannot show.