Skip to main content
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.
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.
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.

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”:

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:

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:
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.