A pin sets an exogenous value. But you don’t have to pin everything — a field you don’t set falls back to a synthesized default, so a suite runs green with zero setup. You pin only the values your test actually cares about.

How a value is resolved: pin → tape → synth

When ProdBreak needs an out-of-control value, it looks for a source in this order and takes the first that matches:
1

A pin you set

An explicit value you pinned for this result (most-specific match wins). This is the surface you use day to day — the rest of this page.
2

A tape (roadmap)

A sequenced answer for the “same call, evolving result” case — e.g. a running total that changes across repeated calls. Order-dependent by nature, so it’s post-MVP; reach for a pin first.
3

Synth — the default fallback

If you pinned nothing, ProdBreak fills the field with a synthesized value. It’s the floor every out-of-control field lands on.

What synth gives you

A synth value is deterministic and shape-correct — it matches the field’s declared schema (type, format, enum), and it’s keyed by (world, operation, field) so the same field comes back the same value on every run. That’s what lets a whole suite pass without a single pin: nothing is random, nothing flakes, and you only pin the fields an assertion actually depends on.
// no pin set for output.due_date → synth fills it, identically every run
const run = await client.taskRuns.create({ taskId: "task_rent" });
// run.output.due_date is shape-correct and stable; pin it only if a test asserts on it
Synth fills, it never lies. Some fields are declared empty-by-default when an empty value is the honest representation (a not-yet-produced result) — ProdBreak leaves those null rather than inventing data, and never randomly nulls a field. Synth invents a plausible leaf value; it never invents history or causality. (How faithful is it? →)

Pin by result, never by channel

You pin against a result key — an operation plus a field_path — never against “the webhook” or “the GET”. There is no verb that names a channel.
await sandbox.exogenous.pin(
  { operation: "runTask", credential_id: "cred_alice" }, // optional narrowing
  "output.balance",
  1432.18,
);
This is the data-side mirror of causes-not-effects: pin the result, let every channel render it. Because the value lives in exactly one place, the webhook payload, the GET, and the list cannot disagree — a divergent fixture is literally inexpressible.
output.balance := 1432.18   ◄ one place the value lives
   ├─ webhook renders it → 1432.18
   ├─ GET renders it     → 1432.18
   └─ list renders it    → 1432.18

Match specificity is the scoping knob

The most-specific matching pin wins. That single mechanism handles both “give everyone the same value” and “give this one user a different value”:
// everyone gets 1432.18...
await sandbox.exogenous.pin({ operation: "fetchBalance" }, "output.balance", 1432.18);
// ...except Alice, whose more-specific pin wins for her calls
await sandbox.exogenous.pin({ operation: "fetchBalance", credential_id: "cred_alice" }, "output.balance", 0);

Author in the vocabulary you think in

Developers think “when the webhook fires, return $1000.” That’s fine — the webhook firing is the producing transition. The on(...) sugar accepts exactly that phrasing and compiles to the same result-keyed pin:
await sandbox.exogenous.on("task_run.completed", "output.balance", 1000);

The resolution trace: the mock teaches its own surface

After any call, ask why a field came back the way it did. The trace names every exogenous field, where its value came from (a pin you set, or synth), and the match that fired:
const trace = await sandbox.lastResolutionTrace();
// {
//   operation: "runTask",
//   outcome: "normal",
//   exogenous: [
//     { field_path: "output.balance",  source: "pin",   value: 1432.18 },
//     { field_path: "output.due_date", source: "synth",  key: "world+runTask+trun_8a2+output.due_date" }
//   ]
// }
You rarely need to read pack docs first. Run the call, read the trace — it tells you “this response had one exogenous field, output.balance, served by synth; pin it with…” The override surface is discoverable at runtime.
Pins cover the common case. For the rarer “same request, evolving answer” (a running total over repeated identical calls), there’s a sequenced primitive — but it’s order-dependent by nature and post-MVP. Reach for a pin first; narrow it by input or credential when calls differ.