Shadow Capture, Replay and Fidelity
Adapted from textbook chapters 7–9. Shadow's acquisition goal is the widest supported recording across JVM, kernel/eBPF, JavaScript, Workers, Kubernetes, state and clients, as defined by the Capture fabric. This page defines how that evidence becomes a qualified replay. Sampling can describe a cohort; a replay needs the operations, values and ordering required by its selected execution cut.
Capture meaningful boundaries
A graph visitor selects a bounded region and compiles capture policy across the available collectors. Runtime hooks observe typed Service/port calls and supported HTTP, binding, storage and event boundaries; deeper JVM, kernel and compiler/runtime adapters add internal and external execution evidence. The visitor does not automatically serialize arbitrary closures or intercept every raw reference call. Capture can retain a broader execution than the boundary selected for substitution during replay.
An operation record names schema/session/run, architectural operation/resource, parent/link references, actor or request scope, sequence/attempt, artifact vector and argument/result references. Payload classification, codecs and truncation/loss are explicit. Large bodies live in approved artifacts; record bodies' consumption/cancellation where replay needs it. Use Service operation descriptors rather than inferring a durable operation name from anonymous code.
Keep trace context as useful correlation, but do not make sampled OTel spans the authoritative replay input. The current exporter has bounded drops and omits span events/links in OTLP JSON. A dedicated journal must state its durable acceptance and completeness. Exporter traffic bypasses ordinary capture to prevent recursion; exporter health still has independent monitoring.
ES6 facade sketch
This textbook example illustrates a narrow async facade. It is proposed adapter code, not a repository implementation or a universal Worker-binding wrapper. Proxies are one part of the JavaScript recording strategy; compiler and runtime instrumentation should acquire supported evidence beyond this facade.
type AsyncMethod = (...args: unknown[]) => Promise<unknown>;
type Facade = Record<string, AsyncMethod>;
interface CallEngine {
call(operation: string, args: readonly unknown[],
live: () => Promise<unknown>): Promise<unknown>;
}
function instrument(api: Facade, engine: CallEngine): Facade {
const methods = new Map<string, AsyncMethod>();
return new Proxy(api, {
get(target, key, receiver) {
const value = Reflect.get(target, key, receiver);
if (typeof key !== "string" || typeof value !== "function") return value;
if (!methods.has(key)) {
methods.set(key, (...args) => engine.call(
key, args, () => Reflect.apply(value, target, args)));
}
return methods.get(key);
},
});
}
The cached wrapper has stable method identity and calls with the original receiver. Its scope must match the engine/request scope; retaining it across requests can retain the wrong context. Frozen properties and Proxy invariants, host-object branding, sync methods, builders and streams require explicit adapters. ECMAScript Proxy semantics.
D1 requires prepared-statement/bind/batch behavior; R2 requires metadata and body stream semantics. Reading a Hyperdrive configuration object does not observe SQL. fetch, RPC/service bindings, timers and entropy need their own qualified adapters. Install request context at the composition boundary; do not mutate globals for each overlapping request. Worker bindings.
Async order and nondeterminism
The same results returned in a different completion order may produce different state. Capture the ordering needed by the selected profile: registration/firing of timers, dependency completion, cancellations, retries, actor/mailbox order and relevant stream events. Record logical IDs and controlled entropy without copying production cryptographic secrets or session credentials.
Install any global shim once and resolve the active request's context through a supported mechanism. Per-request replacement of global clock/RNG functions races concurrent requests. A controlled scheduler may improve replay order, but no adapter should claim full distributed determinism from a single captured HTTP request.
Matching and fidelity
Replay matches semantic operation, resource, causal scope, attempt, sequence and approved argument representation. An ambiguous or missing match is a capture/replay failure. Do not return the nearest response or consume a shared FIFO for unrelated concurrent operations. Replaying against live dependencies on a miss requires a different explicit profile and authority.
Report a fidelity vector rather than a single reassuring score:
| Dimension | What the result must state |
|---|---|
| Initial state | Historical pre-state, pinned sanitized base or latest production-shaped state |
| Boundary coverage | Captured and excluded adapters, raw escapes and missing operations |
| Ordering control | Controlled clocks/entropy/completions and remaining scheduling uncertainty |
| Runtime compatibility | Actual engine/build/device/tool versions and qualified semantics |
| Effect isolation | Verified destinations and substitution owner per boundary |
Capture is bounded by event/byte rates, buffer size, duration and policy. Production keeps serving when capture is unavailable; dropped or truncated evidence marks that session incomplete. A late asynchronous upload error cannot recreate unrecorded bytes. Verification may not turn that incomplete session into a pass.
Qualification cases
Test parallel calls with identical arguments, retries, cancellation, partial streams, thrown errors, reconnect, raw-reference escape, oversized payload, expired policy and exporter failure. Verify one effect despite multiple observers and retain a cleanup receipt for every installed hook. Backend-specific coverage belongs in Backends; assertion semantics belong in Verification.