API Reference
Opt-in runner .check_resources = true checks handles after application
cleanup; sim.control.checkResources() checks at an explicit checkpoint.
See handle-check semantics for scope and failure behavior.
Import the package as:
const mar = @import("marionette");
Marionette is pre-1.0. Import through marionette; the roles below distinguish
application integration, harness controls, and model implementation surfaces.
An exported type is not necessarily an application capability.
API Roles And Compatibility
| Role | Entry points | Intended use |
|---|---|---|
| Application | Host or simulated std.Io, Recorder, Operation, TraceField, TraceValue, Env, Production |
Pass narrow capabilities to application code; use Env and Production at the composition root. Disk / RealDisk remain available for explicit disk-capability integrations. |
| Harness | SimCase, Sim, World.init / simulate, runSimCase, expectSim*, StateCheck, Control, process lifecycle helpers, fault/profile options |
Construct and drive simulations, inject faults, check properties, and own cleanup. These powers belong in tests. |
| Evidence | RunReport, DecisionTape, ReplayCapsule, ReplayIdentity, reduceSimCase, runCampaign, artifact writers, trace summaries |
Retain failures, exact-replay complete executions, run bounded campaigns, and reduce failures within a budget. Call each owned result's deinit. |
| Experimental and model | Endpoint(Message), SimDisk, SimClock, direct model operations and World engine fields |
Explore protocol models or test simulator contracts. Typed endpoints do not promise production transport parity; prefer std.Io for application behavior. |
| Implementation | Source-module internal namespaces, supervisor wiring, I/O backend, scheduler and fiber internals |
Used by simulator components and white-box tests; not a downstream integration contract. |
Breaking export changes are planned and documented with migration guidance. Pre-1.0 APIs may still change, so pin the package revision.
API compatibility is separate from replay compatibility. A capsule still needs matching build, SUT/input, Zig, target, optimization, and disk-model identities. Unchanged trace and capsule formats do not authorize cross-build replay. Keep the matching harness with retained artifacts; see Decision Tapes.
Simulation Runner
runSimCase is the primary runner. It creates a world, constructs the
simulation, initializes application state, runs the scenario and configured properties,
then repeats the execution using the first execution's semantic decision tape
and compares traces.
const Case = mar.SimCase(App);
fn init(sim: mar.Sim) !App {
return App.init(sim.env.io(), sim.env.recorder());
}
fn scenario(case: *Case) !void {
try case.app.work();
try case.control().tick();
}
fn invariant(case: *const Case) !void {
if (!case.app.valid()) return error.InvalidState;
}
const checks = [_]mar.StateCheck(Case){
.{ .name = "application state is valid", .check = invariant },
};
test "scenario" {
try mar.expectSimPass(.{
.allocator = std.testing.allocator,
.seed = 0xC0FFEE,
.simulate = mar.World.SimulateOptions{},
.init = init,
.scenario = scenario,
.checks = &checks,
});
}
Required runner fields are allocator, simulate, init, and scenario.
simulate must be a mar.World.SimulateOptions value. Optional fields are
seed, seed_schedule, start_ns, tick_ns, name, tags, attributes,
checks, watchdog, and check_resources.
runSimCasereturns an ownedRunReport.expectSimPassaccepts only a passing replay.expectSimFailureaccepts a replayable failure; an optionalfailure = FailureExpectation{ ... }constrains its kind, error name, and/or check name.expectSimFuzzrepeats a case across a derived seed sequence; addseeds.expectTraceContainsprints the trace tail when an expected event is absent.
Call RunReport.deinit. A passed report contains the first trace, event count,
and owned decision tape. A failed report contains its failure kind,
error/check names, owned decision tape, partial first trace, and a second trace
when replay diverged. takeDecisionTape transfers tape ownership.
RunFailureKind includes scenario and check errors, structured scheduler
deadlocks/errors, deterministic step_budget_exceeded failures,
watchdog-classified non_yielding/livelock/worker_crashed failures,
replay_diverged, resource_leak, and the three trace/outcome
replay-mismatch kinds.
WatchdogOptions enables optional worker-process containment with
stall_timeout_ns, run_timeout_ns, trace_capacity, and result_capacity bounds. It is
available on Linux, macOS, the supported BSD hosts, and illumos. The watchdog
uses host monotonic time outside simulation and should be enabled only from a
single-threaded harness process. Unsupported hosts return
error.WatchdogUnavailable; invalid or unrepresentable bounds return
error.InvalidWatchdogOptions. error.WatchdogTraceTooLarge indicates that
the encoded completed execution (including its tape) could not fit the worker
result channel.
Scenario State
SimCase(App) contains:
sim: theWorld.Simulationreturned byWorld.simulate;app: state returned by the configured initializer;env(): the node-zero application environment;control(): harness-only fault and scheduling controls.
If App defines pub fn deinit, the runner calls it after every replay.
StateCheck(State) contains a unique, nonempty stable property name, a
function taking *const State, and a phase: CheckPhase (after_init,
after_scenario, both, checkpoint, or always). The default is after_scenario. Check errors
become check_failed reports; invalid names return InvalidStateChecks.
See Property Lifecycle for ordering and cleanup.
Application Capabilities
Env supplies the remaining Marionette-aware capabilities:
io()returns deterministicstd.Ioin simulation or caller-provided host I/O in production;allocator()returns the configured application allocator;recorder()returns a narrowRecorder;recordwrites one structured trace event;buggifyevaluates a named simulation-only fault hook.
Recorder.none() drops events. Simulation environments return a recorder
backed by the world's trace. Production-shaped libraries should usually accept
std.Io, std.Io.Dir, and Recorder rather than all of Env.
Production.init combines caller-owned host I/O, a root directory, an
allocator, and an optional recorder into an Env. Call deinit when finished.
World And Simulation
World.init(allocator, options) owns scheduled randomness, virtual time, trace
bytes, and simulator resources. Call deinit. World.Options.seed_schedule
borrows a strictly ordered SeedSchedule for the lifetime of the world.
Traced harness/model randomness uses World.randomU64, World.randomBool,
World.randomIntLessThan, or World.randomBytes. Application randomness uses
std.Random.IoSource over Env.io() so the same schedule covers both layers.
World.simulate(options) is one-shot and returns Sim:
sim.envis the node-zero application environment;sim.controlis the harness authority;sim.envForNode(node)returns process-scoped I/O;sim.endpoint(Payload, node)opens an experimental typed endpoint;- process registration, kill, restart, and liveness transition helpers live on the simulation value.
World.SimulateOptions configures allocation faults, disk geometry/latency,
optional network topology, task stacks, start jitter, fiber overflow
diagnostics, and an optional deterministic step_budget (see
Step Budget).
World.Options.decisions selects decision recording (the default) or exact
replay from a borrowed []const Decision. chooseU64, chooseBool, and
chooseIntLessThan require stable semantic site IDs. decisionTape borrows
the current entries, cloneDecisionTape returns owned storage, and
finishDecisionReplay rejects an unconsumed replay suffix. See
Decision Tapes.
Control
Control contains:
allocation: fault configuration and address-free statistics;disk: crash/restart and disk-fault controls;network: loss, latency, clogs, partitions, link and node state;process: process crash/restart dynamics;tasks: cooperative scheduler inspection;tick,runFor, andrunTasksUntilIdlefor virtual-time execution.
Harness authority must not be stored in production application state.
Profiles And Metadata
SimProfile expands named workload profiles into simulate options, tags, and
typed replay attributes. runAttribute(key, value) accepts strings, integers,
booleans, and floats.
Traces And Summaries
World.record and Env.record append line-oriented events. Dynamic text must
be passed through structured fields or encoded so spaces and control bytes do
not invalidate the trace grammar. See Trace Format.
summarize(allocator, trace) builds an owned Summary with event, subsystem,
run metadata, network, and fault counts. Call Summary.deinit.
faultCoverage(trace) returns a FaultCoverage without allocating; see
Fault Coverage.
Seeds And Schedules
parseSeed accepts a decimal u64 or a 40-character Git hash. Hashes map to
the first eight raw digest bytes in big-endian order.
DecisionPoint identifies a traced random-call boundary by simulated time and
microstep. SeedCutover assigns a new seed at one point, SeedSchedule is a
borrowed ordered slice of cutovers, and validateSeedSchedule checks strict
lexicographic ordering. Invalid schedules return error.InvalidSeedSchedule.
Schedules are positional and guaranteed only by the same-build determinism contract; they do not replace a semantic decision tape for durable replay.
Build Tidy
The build API exports addTidyStep and addTidyExecutable. The AST-based scan
rejects configured ambient host authorities while ignoring comments and string
literals. Prefer narrow pattern allowlists to whole-file exemptions.
Persistent Replay
ReplayIdentity identifies the pinned build, workload/SUT, toolchain, target,
optimization mode, and disk model. ReplayCapsule.encode returns caller-owned
JSON bytes from a complete reproducible report; the report retains the simulation
options actually used.
ReplayCapsule.decode owns the parsed artifact; call deinit after use.
replaySimCase(config, &capsule, identity) verifies compatibility and reexecutes
the recorded choices, checking the trace and failure identity. Its config supplies
allocator, initializer, scenario, and original checks. The capsule supplies runtime
options. See Decision Tapes And Replay Capsules.
The unused legacy NetworkOptions topology type was removed in 0.7. Configure
the active runtime through World.SimulateOptions.network instead.
Reduction and Explanation
reduceSimCase(config, ReductionOptions) returns an owned ReductionResult.
reduceCapsule(config, &capsule, identity, ReductionOptions) first replays a
retained capsule exactly, then reduces it under the capsule's own options, so a
fresh process needs only the pinned harness and the artifact.
FailureFingerprint compares full stable failure identities. ArtifactOptions
and writeRunArtifacts persist reports through caller-supplied host I/O.
Recorder.event returns ?EventId; Recorder.beginOperation returns an
Operation span. sim.manageProcess(App, node, initialize) returns world-owned
*ManagedProcess(App) state with automatic restart/reopen and runner cleanup.
See the complete contracts and examples.
Campaigns
runCampaign(config, CampaignOptions) runs a bounded range of runSimCase
seeds and returns an owned CampaignSummary with counts, CampaignStopReason,
one CampaignFailure per distinct failure identity, and CampaignCaseError
entries (with a CampaignCasePhase) for runner errors. CampaignArtifacts
selects a new or resumed evidence directory. Set CampaignOptions.reduction to
reduce each new reproducible failure; its CampaignReduction records the cost
and outcome. CampaignSummary.metrics() returns CampaignMetrics.
loadCampaignSummary(allocator, io, dir) reads a campaign journal without
running anything. See Campaigns.