Architecture
Marionette substitutes deterministic authorities at the same boundaries where
application code already performs I/O or waits. The application sees std.Io
and narrow sibling capabilities. The harness sees explicit simulator control.
Execution Shape
runSimCase
-> World
-> Env / std.Io application authority
-> Control harness authority
-> disk, network, scheduler deterministic models
-> decision tape executable choices
-> trace explanatory evidence
runSimCase constructs this graph twice. It initializes app state from Sim,
executes the scenario, runs named checks, and deinitializes app state. The first
ordinary execution records semantic choices; the second exact-replays them and
compares traces. Failures remain owned data in RunReport.
Authorities
Env contains application-facing I/O, allocation, disk, recording, and
BUGGIFY. Control contains time movement and fault injection. This separation
prevents production code from accidentally acquiring crash, partition, or
scheduler powers.
Prefer passing the smallest application capabilities. Code that only needs
files and trace events should accept std.Io, std.Io.Dir, and Recorder
rather than storing Env.
Deterministic std.Io
The simulated backend implements the subsets needed by current validations:
- clocks, sleeps, randomness, async/concurrent tasks, groups, cancellation, futures, futexes, mutexes, and conditions;
- file and directory operations over the disk model;
- TCP-shaped streams, listeners, connects, reads, writes, deadlines, and process-scoped handles over the network model.
Unsupported operations fail closed. Marionette does not intercept arbitrary host syscalls or make ambient nondeterminism reproducible.
Scheduling And Time
Tasks run cooperatively on guarded fibers. A task switches only at modeled I/O,
wait, or explicit scheduler boundaries. Virtual time moves through tick or
runFor; host wall time is not consulted by simulated application code.
The scheduler is deterministic for a seed. It does not model OS preemption or the CPU memory model, so ordinary thread, sanitizer, integration, and platform testing remain necessary.
Fault Models
Disk, network, allocation, and process models share the world's random stream
and trace. Disabled probabilistic faults consume no draws. runFor stops at
scheduled fault boundaries so automatic changes happen at their exact virtual
times.
Disk recovery follows the named portable_v1 contract. Network streams share
bounded queues and byte-pool capacity. Allocation traces use counters and
sizes, never addresses.
Process Lifecycles
Network and I/O handles are process-scoped. Killing a process cancels its tasks, closes its resources, and invokes its registered lifecycle callback. Restart reruns the initializer against surviving durable state. Harness-owned application memory must be explicitly reset in the lifecycle callback.
Decisions And Tracing
Decision entries identify a semantic site, logical time, microstep, preceding trace event, typed alternatives, and selected value. Every trace event has a global index. Dynamic text is percent-escaped and simulator traces avoid addresses, wall-clock values, unordered iteration, and unstable error formatting. The tape controls replay while the trace explains and verifies the result.
Production Boundary
Production-shaped libraries receive host std.Io, a root directory, and an
optional recorder. Production composes those host capabilities, but
Marionette is not a production runtime and does not ship a socket transport.
Typed Endpoint(Message) is an experimental protocol-modeling surface. It is
not wire parity; socket-facing code should use std.Io.net.
Shared Filesystem Metadata
A simulation's process registry owns one file metadata store. Descriptor access mode, cursor, lock ownership, and process lifetime remain local; the shared lock table is keyed by stable file identity. Rename needs no lock-path rekeying. The simulated disk is the authority for identity, visible length, and modification time, including pending writes. A non-suspending identity lookup refreshes metadata and resolves paths after direct disk mutations. Custom disks may omit this optional lookup; they retain the cached adapter contract. Crash recovery still rediscovers stale metadata under the named disk semantics.
Owned Execution Results
The runner normalizes and owns runtime configuration once across both executions.
Each execution returns one owned trace, decision tape, completion flag, and
pass/failure outcome (execution.zig). The comparator creates the public report
and owns shared metadata once. watchdog.zig handles worker isolation and
bounded transport; execution_codec.zig supplies the versioned payload shared
with persistent capsules. Worker termination explicitly marks an incomplete tape.
The redundant fixed packet runtime and its private EventQueue were removed.
Typed endpoints and streams exercise the active shared network runtime; useful
legacy contract tests now run against that implementation.