Studio 8 — Stress-test and adjudicate
Studio 8
Attack your own result the way a hostile reviewer would, and record what survived.
Studio 8
This studio closes with Milestone 8: Your robustness audit, a short chapter of its own after the lessons. What it asks you to produce. A pre-listed robustness grid, negative tests with their assumptions stated, diagnostics, an adversarial-review record, and your adjudication of what survived.
Studio 8 · Road map
Lesson 1 of this studio · Chapter 24
which alternative versions of your analysis you commit to running and reporting, chosen before you see any of their answers
Chapter 24
You decide the full list of robustness and sensitivity checks you will run, and you decide it before you look at a single result, then report every one of them. Two things separate a defensible finding from a lucky slice: each version has to be a valid analysis on its own, and the looking has to come after the deciding. Pre-listing protects the second; nothing rescues the first. A check you think of later is still worth running, as long as you label it as one you added after seeing results and report it with the rest.
Chapter 24 · Key terms
Shared bias
that common error running underneath an entire curve.
Specification searching
running many analyses and reporting only the one that gave the result you wanted, without disclosing the search.
Chapter 24 · Why this decision matters
Everyone shows me the version of the analysis that worked. I assume that one exists. My real question is what happened to all the reasonable versions you could have run instead, and whether you looked at them before or after you saw this answer.
Chapter 24 · Why this decision matters
Chapter 24 · The concept
Robustness check
re-running the same finding under a different but equally defensible choice, to see whether the answer stays
Sensitivity check
deliberately changing an assumption to see how far the answer moves (Rosenbaum 2002)
Chapter 24 · The concept
Headline estimate: the single number that stands in for your whole finding.
Chapter 24 · The concept
A specification curve plots one estimate under many defensible choices, side by side (Simonsohn et al. 2020) (Steegen et al. 2016).
Chapter 24 · The concept
Chapter 24 · The concept
Chapter 24 · The concept
Chapter 24 · A worked example
Chapter 24 · A worked example
Chapter 24 · A worked example
Chapter 24 · A worked example
Assignment-based null check
re-runs the assignment your study actually performed, with the same group sizes and the same blocking
Permutation check
shuffles labels that were not randomly assigned, and answers something only if the groups were exchangeable
Negative control
points your analysis at an exposure, outcome, or period that should be causally null
Chapter 24 · A worked example
import numpy as np, pandas as pd
SEED = 464
rng = np.random.default_rng(SEED)
n = 4000
offered = rng.random(n) < 0.5
prior = rng.gamma(2.0, 30.0, size=n) # spending before the offer
ordered = rng.random(n) < (0.42 + 0.025 * offered) # the offer moves WHO orders
spend = np.where(ordered, rng.gamma(2.0, 33.0, size=n) * (1 + 0.12 * offered)
+ 0.25 * prior, 0.0) # past spending carries over
ship_tax = np.where(ordered, 6.0 + 0.07 * spend, 0.0)
gross = spend + ship_tax
wholesale = np.zeros(n, dtype=bool); wholesale[0] = True
gross[0] = 9_400.0 # the bulk buyer
df = pd.DataFrame({"offered": offered, "gross": gross, "net": spend,
"prior": prior, "ordered": ordered, "wholesale": wholesale})
def pct_gain(d, col, adjust=False):
v = d[col] - (np.polyval(np.polyfit(d.prior, d[col], 1), d.prior)
if adjust else 0)
a, b = v[d.offered].mean(), v[~d.offered].mean()
base = d.loc[~d.offered, col].mean()
return (a - b) / base * 100
grid = []
for s_label, d in (("all shoppers", df), ("minus the wholesale account",
df[~df.wholesale])):
for m_label, col in (("charged total", "gross"), ("net of shipping/tax", "net")):
for sp_label, adj in (("unadjusted", False), ("adj. prior spend", True)):
grid.append({"sample": s_label, "measurement": m_label,
"specification": sp_label,
"gain %": round(pct_gain(d, col, adj), 1)})
g = pd.DataFrame(grid)
print(g.to_string(index=False))
print(f"\nall eight share ONE estimand: spending per shopper ASSIGNED.")
print(f"range {g['gain %'].min():.1f}% to {g['gain %'].max():.1f}% — a spread of "
f"defensible answers, not a confidence interval")
# the specification that does NOT belong: conditioning on ordering
bad = pct_gain(df[df.ordered], "gross")
print(f"\naveraging over ORDERERS only: {bad:.1f}% — a different estimand, on a")
print("group the offer itself selected. it does not belong on the curve")Chapter 24
Where the tool failed
You paste your four-handle grid and ask an AI to write your robustness section. Back comes a long, well-organized passage that runs the sample handle, the specification handle, and the metric handle, and closes with “the twelve percent result is robust across all standard specifications.” It reads as thorough and complete. The trap is that it never listed the measurement handle, the total amount charged versus shipping and taxes stripped out, and that is precisely the choice that moves your estimate the most. The section looks exhaustive while omitting the one check a reviewer would reach for first.
Chapter 24 · An AI failure case
Chapter 24
This stays yours
Three calls stay yours. You decide which checks you commit to before you look, which flagged issues the data actually confirms, and the final claim, with its per-panel span and its boundary, that you defend. A reviewer, human or AI, proposes. You verify against your own data, and the evidence decides.
Chapter 24 · Your move
Work it in the companion notebook with Chapter 24 open beside it. Log every delegation in your AI Research Ledger.
Lesson 2 of this studio · Chapter 25
which check aimed at a guaranteed zero you run, and what passing it earns you
Chapter 25
You decide which negative test your design actually needs, a check aimed at a place where the answer must be zero, and you decide what a clean pass does and does not license you to say. Picking the test is the research. Running it is the easy part.
Chapter 25 · Why this decision matters
Before you tell me the extract works, show me the plate with no extract on it. If the blank disk also cleared the bacteria, you have not discovered a drug. You have discovered that your solvent is toxic.
Chapter 25 · Why this decision matters
Chapter 25 · The concept
Negative test
a check that runs your exact analysis on a situation where the true answer has to be zero
Artifact
a signal produced by your procedure rather than by the thing you study
Chapter 25 · The concept
Chapter 25 · The concept
A good negative control has to satisfy three conditions, and all three matter (Lipsitch et al. 2010).
Chapter 25 · The concept
Placebo test
you replace the real cause with a fake one that cannot act, and confirm the effect disappears
Falsification test
you check a consequence that must be false if your explanation is right, and confirm it is false
Negative control
you point the same machinery at an outcome your cause could not possibly touch
Vehicle control
the negative control matched to how you delivered the cause: the carrier with the active ingredient left out
Chapter 25 · The concept
Chapter 25 · The concept
The negative tests all share one move: aim the machinery where the answer must be zero, and check that what comes back is ordinary for a world where it is (Rosenbaum 2002).
Chapter 25 · A worked example
Chapter 25 · A worked example
Chapter 25 · A worked example
import numpy as np, pandas as pd
SEED = 464
rng = np.random.default_rng(SEED)
# Zone of inhibition (mm) on identical plates, ten replicates each.
conditions = {
"plant extract in 70% ethanol": 14.0, # what you want to claim
"vehicle only (70% ethanol)": 0.0, # negative control: must be bare
"standard antibiotic": 22.0, # positive control: must show a ring
"extract in 20% ethanol": 11.5, # the redo at lower solvent
}
plates = {c: np.clip(rng.normal(mu, 1.2, size=10), 0, None)
for c, mu in conditions.items()}
print(pd.DataFrame({"condition": list(plates),
"mean zone (mm)": [v.mean().round(1) for v in plates.values()],
"max zone (mm)": [v.max().round(1) for v in plates.values()]})
.to_string(index=False))
vehicle, positive = plates["vehicle only (70% ethanol)"], plates["standard antibiotic"]
print(f"\nnegative control ring : {vehicle.mean():.1f} mm -> "
f"{'PASS' if vehicle.mean() < 1 else 'FAIL, the solvent is doing work'}")
print(f"positive control ring : {positive.mean():.1f} mm -> "
f"{'PASS' if positive.mean() > 5 else 'FAIL, the assay cannot detect anything'}")
print("\nboth controls must be read before the extract's ring means anything.")
print("a negative control that passes on a broken assay proves nothing")Chapter 25 · A seeded simulation
import numpy as np
import matplotlib.pyplot as plt
SEED = 464
rng = np.random.default_rng(SEED)
reps, pairs = 2000, 12
plate = rng.normal(0, 0.4, size=(reps, pairs)) # shared plate effect
blank = 6.3 + plate + rng.normal(0, .35, size=(reps, pairs))
vehicle = 6.3 + plate + rng.normal(0, .35, size=(reps, pairs))
nulls = (vehicle - blank).mean(axis=1) # true vehicle effect: exactly zero
print("exactly zero:", int((nulls == 0).sum()), "of", reps, "experiments")
print("rounds to 0.00 at two decimals:", int((nulls.round(2) == 0).sum()))
print("typical spread (middle 95%):", np.percentile(nulls, [2.5, 97.5]).round(2))
print("this run's reading:", round(float(nulls[0]), 3), "mm")Chapter 25 · A seeded simulation

The null readings pile up around zero and spread from about -0.29 to +0.27 mm; none is exactly zero.
Chapter 25 · A seeded simulation
Chapter 25
Where the tool failed
You ask an AI to design a negative control for your ethanol-based assay. It answers, with complete confidence, “use a disk soaked in sterile water.” You run it, the water disk comes back perfectly clean, and the tidy conclusion writes itself: control passed, effect confirmed. Here is the trap. Water is not the vehicle you used. You delivered the extract in ethanol, so the artifact you needed to catch is ethanol toxicity, and a water disk can never show it. The clean result told you nothing at all.
Chapter 25 · An AI failure case
Chapter 25
This stays yours
You decide which negative test your design actually needs, whether the control is genuinely null (a place your cause truly cannot reach), and what a clean pass licenses you to claim. A tool can list candidate controls, but only you know the mechanism well enough to certify that your cause cannot touch the one you chose. The judgment about what a passed negative test bought you, and how far its sensitivity reached, stays yours.
Chapter 25 · Your move
Work it in the companion notebook with Chapter 25 open beside it. Log every delegation in your AI Research Ledger.
Lesson 3 of this studio · Chapter 26
which of the flaws a reviewer names are real, settled by a check you run rather than by how sure the reviewer sounded
Chapter 26
When a reviewer hands you a list of flaws in your own analysis, you decide which flaws are real, and you decide it by running the one data check that confirms or refutes each, never by going with whichever reviewer sounded most certain. Confidence is not evidence, and a panel of confident reviewers is not three pieces of evidence.
Chapter 26 · Key terms
Specification searching
trying many versions of an analysis and reporting only the one that gave the answer you wanted, without disclosing the search (Simmons et al. 2011).
Correlated error
two reviewers wrong in the same way, so their agreement is an echo, not a confirmation (Peker 2023).
Chapter 26 · Why this decision matters
Chapter 26 · Why this decision matters
Do not tell me the numbers got better. Tell me which check you ran that would have caught it if they hadn’t. I trust the check, not the graph.
Chapter 26 · Why this decision matters
Chapter 26 · The concept
Adversarial reviewer
a reader whose job is to attack your result and find where it breaks, not to praise it
Robustness check
re-runs the same finding under a different but equally defensible choice, and sees whether the answer holds
Placebo test
runs your exact procedure where the effect cannot exist, and asks whether what comes back is ordinary for a world with nothing in it
Chapter 26 · The concept
Chapter 26 · The concept
Chapter 26 · The concept
Chapter 26 · The concept
Chapter 26 · A worked example
Chapter 26 · A worked example
Chapter 26 · A worked example
Chapter 26 · A worked example
import numpy as np, pandas as pd
SEED = 464
rng = np.random.default_rng(SEED)
def waits(n, scale, staffed):
return rng.gamma(2.2, scale * (1.0 if staffed else 1.18), size=n)
mornings = 20
old = pd.DataFrame({"morning": np.repeat(np.arange(mornings), 60),
"wait": np.concatenate([waits(60, 40, m % 5 != 0)
for m in range(mornings)]),
"mix": np.tile(["commuter", "shopper", "family"], 400)})
new = pd.DataFrame({"morning": np.repeat(np.arange(mornings), 60),
"wait": np.concatenate([waits(60, 27, m % 5 != 0)
for m in range(mornings)]),
"mix": np.tile(["commuter", "shopper", "family"], 400)})
p95 = lambda d: np.quantile(d.wait, 0.95)
print(f"headline p95: {p95(old):.0f} s -> {p95(new):.0f} s")
# flaw 1: one unusually quiet morning is carrying the win
loo = [p95(new[new.morning != m]) for m in range(mornings)]
print(f"\nleave-one-out p95 range : {min(loo):.0f}-{max(loo):.0f} s "
f"(the drop survives dropping any single morning)")
# flaw 2: it only holds for one customer mix
by_mix = pd.DataFrame({"old p95": old.groupby("mix").wait.quantile(0.95),
"new p95": new.groupby("mix").wait.quantile(0.95)}).round(0)
print("\n" + by_mix.to_string())
# flaw 3: the placebo — two stretches of the OLD layout against each other
placebo = p95(old[old.morning < 10]) - p95(old[old.morning >= 10])
print(f"\nplacebo, old layout vs itself : {placebo:+.0f} s "
f"({'clean' if abs(placebo) < 25 else 'the machinery manufactures gaps'})")
print("three confident reviewers, three checks, and only the checks decide")Chapter 26 · A worked example
Chapter 26
Where the tool failed
You paste your wait-time summary into an AI reviewer, and it answers with total certainty: your improvement is an artifact of one unusually quiet morning, drop that morning and it vanishes. The claim is specific, mechanistic, and stated without a hedge. It reads exactly like a reviewer who has seen this mistake a hundred times.
Chapter 26 · An AI failure case
Chapter 26
This stays yours
These stay yours, no matter how fluent the reviewer sounds. Which robustness checks you commit to before you look, because only those carry confirmatory weight. A check you think of after seeing the result is still worth running; it is exploratory, and it counts only if you label it that way and report it with the rest. Which flagged flaws your data actually confirm, decided by the measurement and not by the confidence. Whether a flaw is a claim-boundary problem that no further measurement can fix and only a narrower claim can answer. And the one bounded result you will defend, with its uncertainty stated. A reviewer proposes; you verify, and the evidence decides.
Chapter 26 · Your move
Work it in the companion notebook with Chapter 26 open beside it. Log every delegation in your AI Research Ledger.
Lesson 4 of this studio · Chapter 27
what you require of a confident-sounding result before you put your name on it
Chapter 27
When a tool hands you a fluent, confident finding, you decide whether its confidence counts as evidence (it never does) and which independent check you run on the number underneath it before you repeat the claim as your own. Nothing about how sure the sentence sounds tells you whether it is true.
Chapter 27 · Key terms
False confidence
when fluent, detailed output makes you feel sure of something you never actually verified (Ji et al. 2023).
Automation bias
the tendency to over-trust an automated system and stop checking, precisely because a machine produced the answer (Goddard et al. 2012).
Illusion of understanding
mistaking a smooth explanation for real comprehension (Rozenblit & Keil 2002).
Verification
an independent check, run by a method outside the model, that a result is actually true.
Chapter 27 · Why this decision matters
I don’t act on the sentence. I act on the number behind it.
Chapter 27 · Why this decision matters
Chapter 27 · The concept
Chapter 27 · The concept
Automation bias
the tendency to over-trust an automated system and stop checking, precisely because a machine produced the answer
Illusion of understanding
mistaking a smooth explanation for real comprehension
Chapter 27 · The concept
Chapter 27 · The concept
Verification
an independent check, run by a method outside the model, that a result is actually true
Chapter 27 · The concept
Chapter 27 · A worked example
Chapter 27 · A worked example
The riparian buffer reduced average nitrate by about 40 percent, a clear environmental success.
Chapter 27 · A worked example
Chapter 27 · A worked example
Chapter 27 · A worked example
import numpy as np, pandas as pd
SEED = 464
rng = np.random.default_rng(SEED)
weeks = np.arange(104)
season = 1.1 * np.sin(2 * np.pi * weeks / 52)
after = weeks >= 52
nitrate = 8.1 - 1.35 * after + season + rng.normal(0, 0.6, size=104)
before_mean, after_mean = nitrate[~after].mean(), nitrate[after].mean()
drop = before_mean - after_mean
print(f"before-planting mean : {before_mean:.1f} mg/L")
print(f"after-planting mean : {after_mean:.1f} mg/L")
print(f"drop : {drop:.1f} mg/L, {drop/before_mean*100:.0f}%")
print("the confident summary said: 40%")
print("\nand the number you just computed still cannot be pinned on the buffer:")
print("one site, no comparison stream, and a wet spring would look the same")Chapter 27
Where the tool failed
You ask the AI to “summarize what this water-quality dataset shows,” and it hands back a confident paragraph ending in “a 40 percent reduction, a clear success.” Every part reads like a finding you could quote: a round figure, a firm verdict, no hedging.
Chapter 27 · An AI failure case
Chapter 27
This stays yours
These stay yours, no matter how sure the tool sounds. Deciding what claim the verified number actually supports, and how far that claim reaches. Judging whether your design lets you credit the buffer for the change, or only lets you report that nitrate fell. Stating the uncertainty and the limits in your own words. The tool can draft a summary; deciding whether that summary is true, and answering for it, is the researcher’s job.
Chapter 27 · Your move
Work it in the companion notebook with Chapter 27 open beside it. Log every delegation in your AI Research Ledger.
Studio 8 closes here
What the lessons handed you becomes one artifact you can defend.
Milestone 8
The artifact
What this milestone produces. A pre-listed robustness grid, negative tests with their assumptions stated, diagnostics, an adversarial-review record, and your adjudication of what survived.
Milestone 8 · Check before you start
Milestone 8 · In the studio
Milestone 8 · Every studio, these four
Ethics, permissions, and data exposure
Report the checks that hurt your finding as fully as the ones that helped.
Evidence, provenance, and reproducibility
A flag is real when a check confirms it, not when the reviewer sounds certain.
AI activity, verification, and human decisions
An AI reviewer is another critique, not independent verification.
Uncertainty, claim boundary, and revision history
Specification spread measures your choices; it is not an uncertainty interval.
Milestone 8
How the record works
Your milestone artifact is a dated, numbered version with the reason for the version attached. When later evidence changes it, you write the next version rather than editing the last one, because the sequence of changes is itself part of your research record.
AI is your arm and your research assistant, not your brain.
AI can review AI, and a second model is a real auditor of the first. The last decision is always human.

EDR|AI · Studio 8 — Stress-test and adjudicate