Overview

Getting started

Pin the crate version, construct a SystemClock, and read time through one injected Clock — then swap in a VirtualClock for tests with no other code changes.

Add the dependency

Add FerroReplay from the approved MorphIQ package source for your workspace. Consumers pin a version and upgrade deliberately:

[dependencies]
ferro-replay = { version = "0.1" }
Versioning

Tags are semver. Breaking the Clockcontract is a major bump; the determinism laws (monotonicity, wakes-before-further-advance) are load-bearing for every consumer's replay correctness, so upgrades are deliberate.

Construct the live clock

Build one SystemClock at the composition root and hand it around as Arc<dyn Clock>. It anchors the epoch timeline to a monotonic instant once at construction, so now_ns never goes backwards across NTP steps and costs no syscall per call.

use std::sync::Arc;
use std::time::Duration;
use ferro_replay::{Clock, SystemClock};

let clock: Arc<dyn Clock> = Arc::new(SystemClock::new());

// Read the current time, nanoseconds since the UNIX epoch.
let now_ns = clock.now_ns();

// Sleep relative to now — e.g. an I/O-timeout race or retry backoff.
clock.sleep(Duration::from_millis(250)).await;

// Or wait until an absolute deadline on the same timeline.
clock.sleep_until(now_ns + 1_000_000_000).await; // +1s

Swap in virtual time for tests

Because every component already depends on Arc<dyn Clock>, a test simply constructs a VirtualClock instead. Advancing time wakes the sleeper, which observes now equal to its own deadline — and advancing before the sleeper registers still wakes it, so the test is race-free with no real waiting.

use std::sync::Arc;
use ferro_replay::{Clock, VirtualClock};

#[tokio::test]
async fn wakes_at_its_deadline() {
    let clock = Arc::new(VirtualClock::new(1_000));
    let c = clock.clone();
    let task = tokio::spawn(async move {
        c.sleep_until(2_000).await;
        c.now_ns()
    });

    clock.advance_to(2_000).await;
    assert_eq!(task.await.unwrap(), 2_000);
}
Runtime

The strict ordering guarantees hold on a current-thread runtime — the default #[tokio::test] flavor. A multi-thread runtime would reintroduce wake-order races, so a replay binary structurally selects the current-thread scheduler.

Where to go next