One input boundary. Every mode.
Live, Record, Replay, and Scenario drive the same computation through canonical application-owned inputs. Replay is evidence, not a second implementation of the domain.
Admission is the determinism boundary
The application owns the meaning and encoding of an observation. Once admitted, runD can give it stable identity, order, recording, and replay. Native wall-clock arrival and operating-system ready-list order remain outside that boundary.
Mode behavior is exact
| Mode | Input origin | Source calls | Fallback |
|---|---|---|---|
| Live | Application source | Exactly once per row | Not applicable |
| Record | Application source + retained record | Exactly once per row | Not applicable |
| Replay | Retained canonical record | Zero | None |
| Scenario | Retained Record + validated Choice patches | Zero | None |
A checked Record → Replay path
#include <rund/replay.hpp>
#include <array>
#include <cstddef>
#include <cstdint>
namespace {
int Replay(rund::Session &session) {
std::byte produced = std::byte{7};
std::uint32_t source_calls = 0u;
bool simulation_ok = false;
rund::replay::Binding replay{};
auto source = [&](rund::replay::Writer &writer) -> std::uint64_t {
++source_calls;
const std::array bytes{produced};
(void)writer.append(bytes);
return 1u;
};
const auto commands =
replay.input(rund::replay::Input{.id = 1u, .schema = 1u}, source);
auto simulate = [&](rund::replay::Context &context) {
const auto value = commands.read(context);
simulation_ok = value && value.sequence() == 1u && value.size() == 1u &&
value.bytes()[0] == std::byte{7};
};
const auto recorded = rund::replay::record(session, simulate);
if (!recorded) {
return recorded.exit_code();
}
produced = std::byte{255};
simulation_ok = false;
const auto replayed = rund::replay::run(session, recorded, simulate);
if (!replayed) {
return replayed.exit_code();
}
return simulation_ok && source_calls == 1u ? 0 : 2;
}
} // namespace
int main() {
rund::Session session{};
const auto opened = session.open(rund::SessionConfig{.workers = 1u});
if (!opened) {
return opened.exit_code();
}
const int operation = Replay(session);
const auto closed = session.close();
if (operation != 0 && operation != 2) {
return operation;
}
return closed ? operation : closed.exit_code();
}
The source is called once while recording. After the source value changes, strict Replay still returns the recorded byte and makes zero additional source calls.
Scenario changes input, not the simulation path
A Choice belongs to the bound Channel, so id, schema, sequence, and replacement bytes cannot drift apart. Scenario validates and freezes all choices before restore or simulation.
std::array replacement{std::byte{0x2a}};
std::array choices{commands.choice(sequence, replacement)};
auto changed =
rund::replay::scenario(session, baseline, choices, simulate);
if (!changed) return changed.exit_code();
Duplicate, missing, ambiguous, invalid, or over-capacity rows fail without invoking the
callback. Inspect matches() only after a successful Scenario.
Checkpoints make divergence local
A stateful Binding owns one checkpoint schema and one borrowed restore codec. Checkpoint state is copied into immutable storage; continuation commits its start/state lineage so an unrelated Record cannot be spliced into the chain.
input identity
→ retained Record
→ checkpoint state hash + lineage
→ continued Record
→ next checkpointauto checkpoint = replay.checkpoint(baseline, state_bytes);
if (!checkpoint) return checkpoint.exit_code();
auto resume = replay.resume(checkpoint);
if (!resume) return resume.exit_code();
auto continued = resume.record(session, simulate);
if (!continued) return continued.exit_code();
rund::replay::History history{rund::replay::Retention{
.max_segments = 64,
.max_bytes = 16 * 1024 * 1024,
.max_events = 4096,
}};
if (!history) return history.exit_code();
auto added = replay.append(history, std::move(continued), state_bytes);
if (!added) return added.exit_code();
History stores a bounded ring of immutable Record/Checkpoint segments. Segment
count, retained bytes, and evidence rows are all explicit limits; eviction preserves a
contiguous suffix and does not mutate copied Segment values.
Inspect the first divergent boundary
check performs strict equality. diff reports typed differences, and
window exposes bounded context around the first observation, host-event, input,
transcript, or trace mismatch. Save/load APIs enforce explicit limits before publishing
immutable evidence.
Continue to Runtime for Session shutdown and telemetry, or inspect the optional checked Scenario, checkpoint, and History examples ↗.