File size: 25,806 Bytes
9762da0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# seqeval — Efficient Sequential Regression Detection: Technical Design

**Goal.** Detect small model regressions (~2 percentage points on specific
classes/conditions) on every model update WITHOUT evaluating the full corpus,
with statistical validity under (a) sequential looks, (b) clustered
(non-independent) frames, (c) stratified non-proportional sampling, and
(d) many simultaneous hypotheses.

This document records the design *and the judgment calls*: which candidate
techniques were adopted, which were rejected, and why. The reference
implementation lives in this package and is exercised by `tests/test_seqeval/`.

---

## 1. Problem formulation

For each metric m (recall is the driver metric in v1) and each population
stratum h, let Δ_h = m_candidate(h) − m_baseline(h) measured on the same data.

With a **practical-significance margin** δ (default 0.005 = 0.5pp):

```
H0 (no material regression):  Δ_h ≥ −δ
H1 (material regression):     Δ_h < −δ
```

plus the separate equivalence-style claim used for PASS: reject `Δ_h ≤ −δ`
in favor of `Δ_h > −δ`.

**Absolute vs relative deltas.** The default driver is **absolute percentage
points**, for three reasons:

1. Safety cost is absolute: a 2pp recall drop means the same number of extra
   missed pedestrians whether baseline recall was 0.98 or 0.80. Relative
   deltas overstate changes on strong baselines (0.98→0.96 is "only −2%
   relative") and understate them on weak ones.
2. Absolute deltas keep Bernoulli variance algebra and margin semantics simple
   and uniform across strata; relative margins make the null hypothesis depend
   on the (estimated) baseline value, contaminating the test with baseline
   estimation error.
3. Absolute margins are what promotion policies elsewhere in this platform
   already use (`megaeval.analysis.DEFAULT_PROMOTION_POLICY` is expressed in
   absolute drops), so the gate composes with existing policy language.

Relative deltas are still *reported* in every evidence record (`rel_delta`)
because they are useful for human interpretation, especially for rare classes
with weak baselines. They are never the decision variable.

**Practical vs statistical significance.** With ~320k objects, a 0.05pp drop
can be "statistically significant" yet operationally meaningless. Hence the
margin δ: nothing is declared REGRESSION unless the evidence shows the drop
exceeds δ, and nothing is declared PASS unless the evidence shows any drop is
smaller than δ. Statistical significance against a point null of exactly zero
is deliberately not used anywhere.

## 2. The statistical unit: clusters, not frames

Frames/objects are not independent: objects in one container (scene/track)
share weather, lighting, scenario, scene-level hardness and — critically —
correlated model failures (one bad scene produces many misses). Treating
objects as iid understates variance by the design effect

```
DEFF = 1 + (m̄ − 1) · ICC        (m̄ = size-weighted mean cluster size)
```

and every "significant" result computed that way is anti-conservative.

Adopted design:

* **Inference unit = cluster**: all sampled objects of a stratum in one
  container. Per-cluster mean paired differences (bounded in [−1, 1]) are the
  observations fed to the confidence sequences and e-processes. This is valid
  under *arbitrary* within-cluster correlation — no correlation model is
  assumed — and whole clusters are sampled so the unit is never split
  (`planner.py` selects container groups, not objects).
* **Diagnostics, not corrections**: ICC (one-way ANOVA estimator), DEFF and
  n_eff = n/DEFF are computed per node (`units.py`) and attached to every
  evidence record. They are reporting artifacts; validity does not depend on
  them because clustering is handled structurally.
* An interesting empirical note the machinery surfaces: pairing itself removes
  most cluster correlation (the shared scene effect hits both models and
  cancels in d_i), so ICC on paired differences is small (~0.07 in the worked
  example, DEFF ≈ 1.2) even when ICC on raw outcomes is large. We keep the
  cluster unit anyway — the residual correlation is real and free to handle.
* **Clustered bootstrap** (resampling clusters, not objects) is the prescribed
  fallback for metrics that don't decompose into per-object outcomes (§3). It
  is not needed for the v1 recall driver.

Rejected: variance-inflation-only corrections (divide n by an assumed DEFF≈2
and pretend units are iid). Rejected because ICC varies by stratum and model
pair; a fixed inflation is either wasteful or invalid, and it composes poorly
with anytime-valid methods whose guarantees are per-observation.

## 3. Paired evaluation as the core design

Both models are evaluated on the **same sampled units** (`paired.py`), and all
inference is on the per-unit difference d_i = c_i − b_i ∈ {−1, 0, +1}.

Why this is the single biggest efficiency lever: for two independent samples,
Var(m̂_c − m̂_b) ≈ 2·p(1−p)/n (~0.18/n at p=0.9). For paired evaluation,
Var(d) = ν − Δ², where ν = P(models disagree) — the discordance rate. Adjacent
model versions agree on almost everything (measured ν ≈ 0.03–0.06 in the
harness), so pairing cuts required samples by roughly 3–10×. The paired
design also removes the between-unit difficulty variance *and* the shared
scene effects (§2).

**McNemar correspondence.** For binary per-object outcomes the information in
the paired design is exactly the discordant counts: n01 (baseline right,
candidate wrong) vs n10 (the reverse) — McNemar's setup. We do not use the
classical McNemar *test* (it is fixed-n); instead the same discordant-pair
information drives (a) the sequential e-process on d_i and (b) the
Beta-Binomial posterior of §14, which is literally a Bayesian McNemar. The
counts n01/n10 are reported in attribution because they are the most
interpretable summary of "what changed".

**Which metrics get which treatment:**

| Metric | Per-object outcome? | Sequential treatment |
|---|---|---|
| recall (per-GT-object TP/FN) | yes, Bernoulli | confidence sequence / e-process on paired d_i ✔ (v1 driver) |
| precision (per-prediction) | yes, but the unit set differs per model (each model's own predictions); pair at the cluster level | CS on cluster-level paired precision differences |
| safety_recall, per-stratum recall | yes | same as recall ✔ |
| mean IoU (matched pairs) | bounded continuous per object | CS on paired IoU differences (empirical-Bernstein handles [0,1] naturally) |
| AP / mAP / AUC | **no** — rank statistics over the whole set; not a mean of per-object terms | **paired clustered bootstrap** at checkpoints (resample clusters, recompute both models' AP on the same resample, CI on the difference). No anytime-valid guarantee: schedule at *pre-registered* checkpoints (end of each budget stage) with alpha-spending across the (few, fixed) looks |

This split is a deliberate honesty boundary: Bernoulli-style anytime-valid
treatment is used exactly where a metric is a mean of bounded per-unit terms;
composite rank metrics get bootstrap at a small number of pre-committed looks
rather than pretending they support continuous monitoring.

## 4. Sampling design: hybrid Neyman + risk floors + safety minimums

Candidates considered:

* **Proportional allocation** — matches the population, but starves exactly
  the strata we care about (pedestrian|night is ~5% of objects; a proportional
  10k sample gives ~500 units, hopeless for 2pp detection). Rejected as the
  primary rule.
* **Equal allocation** — maximal per-stratum power for fixed budget, but
  wastes budget on huge easy strata and makes population-level estimates
  needlessly noisy. Rejected.
* **Risk-weighted only** (as in `megaeval.sampling`) — right instinct, no
  optimality story; can be arbitrarily far from variance-optimal. Rejected as
  the *sole* rule, kept as a multiplier.
* **Neyman allocation** n_h ∝ N_h·σ_h — variance-optimal for the *population*
  estimate, but per-stratum guarantees are incidental, and σ_h must not come
  from candidate outcomes (bias firewall, §13). Adopted as the baseline using
  a candidate-independent PRIOR variance profile (documented rates by
  class/condition; a pilot baseline-only estimate would also qualify).

**Adopted hybrid** (`planner.py`), in order: Neyman shares from prior σ_h →
multiplicative risk weights (VRU ×1.5, night ×1.3) → mandatory minimum per
stratum (200) → pre-registered safety-critical strata get a floor sized for
the target MDE (2500 by default, §10) → truncate at N_h, select whole
clusters in a seeded shuffled order.

**Unbiasedness under oversampling.** Every sampled object carries the
Horvitz–Thompson weight w_h = N_h/n_h. Population and class-level estimators
are weight-corrected (weighted cluster means feed the overall/class/difficulty
nodes), so the classic stratified identity `E[Σ_h W_h p̂_h] = p` holds and
oversampling safety strata does not bias headline estimates. Per-stratum
(leaf) inference is self-weighting and needs no correction.

## 5. Multi-stage pipeline

```
sanity (≈500 units)  →  stratified screening (~15% of plan)
    →  sequential confirmation (batched to 100% of plan, anytime-valid)
    →  targeted escalation (reserve clusters, suspect/safety strata only)
```

* **Sanity**: fingerprints resolved, frozen plan hashed, smoke batch checks
  for degenerate outputs (rate ∉ (0.2, 1], discordance ≥ 0.5 ⇒ abort — a
  broken export should fail in seconds, not consume the budget).
* **Screening** flags "suspect" strata early (e-value ≥ 1.5 — soft evidence,
  *never* a decision) to prioritize escalation later. Screening consumes the
  same frozen plan; it does not select units based on outcomes (§13).
* **Sequential confirmation** processes the remaining plan in batches; all
  decisions (REGRESSION / PASS) can fire at any batch boundary because the
  underlying processes are anytime-valid.
* **Escalation** pulls from each stratum's pre-frozen reserve (shuffled at
  plan time) for strata still undecided that are suspect or safety-primary,
  up to a hard cap. Escalation to *full* evaluation of a stratum is the
  terminal step of the same mechanism (the reserve is the rest of the
  stratum).

## 6. Sequential methodology: anytime-valid confidence sequences, not CUSUM

**Why repeated fixed-sample CIs are wrong.** A 95% CI computed at n has 5%
miscoverage *at that single n*. Recompute after every batch and stop on the
first exclusion of the null and the type-I error inflates monotonically —
by the law of the iterated logarithm the statistic crosses any fixed-n
boundary infinitely often under the null, so "peek until significant" has
error probability → 1. Evaluation gates that check after every batch are
exactly this setting.

**Adopted: confidence sequences / e-processes** (`sequential.py`):

* Empirical-Bernstein confidence sequence (Waudby-Smith & Ramdas 2023,
  predictable plug-in): a CS valid *simultaneously over all n*, so the gate
  may look after every batch and stop whenever it likes. Empirical-Bernstein
  rather than Hoeffding because paired differences have tiny variance
  (ν ≪ 1/4) and EB width scales with the realized variance; a Hoeffding CS
  would be ~3–4× wider here (Hoeffding rejected for power, kept as a mental
  baseline).
* One-sided **betting e-processes** for the two directed nulls. The wealth
  process K_t = Π(1 − λ_t(X_t − m)) is a nonnegative supermartingale under
  H0: μ ≥ m for any predictable λ_t in the admissible range; Ville's
  inequality gives P(∃t: K_t ≥ 1/α) ≤ α. Bet sizing = max(empirical-Bernstein
  recipe, aGRAPA/approximate-Kelly λ ≈ drift/(σ̂² + drift²)), clipped —
  sizing affects power only, never validity. E-values (not just CIs) are kept
  because they compose: they feed e-BH in the hierarchy (§8) and multiply
  across independent looks/runs if evidence is ever pooled.
* Batch-incremental updates, sticky decisions, per-batch trajectory recording
  (`log e vs n` with the 1/α boundary) for the dashboard.

**Where CUSUM legitimately fits — and why not here.** CUSUM/Page tests are
designed for *change-point detection in an ongoing stream*: an unknown time
at which a monitored process shifts, with guarantees phrased as average run
length (ARL) to false alarm and detection delay. That is the right tool for
**production drift monitoring** (a deployed model's live metric degrading at
an unknown time — a reasonable future addition next to
`sensorflow/evaluation/regression.py`). It is the wrong tool for **gated A/B
evaluation**, where there is no change-point: the candidate is a fixed,
already-different model, the hypothesis is about a fixed mean difference, ARL
semantics don't map to per-release type-I error, and CUSUM provides neither
confidence intervals for effect size nor e-values for hierarchical multiple
testing. CUSUM is therefore explicitly rejected for the gate.
(Group-sequential designs — O'Brien-Fleming etc. — were also considered:
valid, but they require pre-committing to a fixed number of looks and a
maximum n, which fights the budget-staged, stop-any-time controller; the
anytime-valid framework strictly dominates for this use.)

## 7. Mandatory three-outcome decisions

Non-detection is NEVER equivalence. Each node ends in exactly one of:

* **REGRESSION** — the regression e-process crossed 1/α_allocated: confirmed
  drop beyond the margin. Gate: block.
* **PASS** — the equivalence e-process crossed 1/α_pass: confirmed that any
  drop is smaller than the margin (this is a one-sided equivalence /
  non-inferiority claim, the sequential analogue of a TOST bound). Gate:
  allow, but only when the overall node AND every pre-registered safety
  primary individually PASS.
* **INSUFFICIENT_EVIDENCE** — budget exhausted first. Gate: expand within
  budget if possible, else report with explicit language: *"not proven
  equivalent — do not treat as a pass."* (`controller.INSUFFICIENT_LANGUAGE`
  is exactly that sentence; the tiny-budget test asserts it.)

## 8. Hierarchical multiple testing

Testing ~28 nodes (overall, 6 classes, 18 class×condition strata, 3 difficulty
bands) at α each would give a family error of ~1−0.95²⁸ ≈ 76%. Naive
Bonferroni over everything destroys power for the one node that matters.
Adopted structure (`hierarchy.py`):

```
Level 1  overall                        α·0.30   single test
Safety   pre-registered primaries       α·0.30   split equally, tested individually
Level 2  class                          α·0.15   e-BH within level
Level 3  class × condition strata       α·0.15   e-BH within level (non-safety)
Level 4  difficulty bands               α·0.10   e-BH within level
```

Budgets sum to α, so the union bound gives family-wise control across levels;
within a level, **e-BH** (BH on e-values) controls FDR *under arbitrary
dependence* — mandatory here because levels re-aggregate the same units and
strata share scenes. e-BH composes with optional stopping because an
e-process value at any stopping time is a valid e-value (this is the reason
the implementation carries e-values and not just CSs).

**Gatekeeping and the masking trap.** Classic top-down gatekeeping ("test
children only where the parent rejected") was **rejected** for the regression
direction: a 2pp pedestrian-night drop inside a globally *improved* model is
invisible at level 1, and a gate that only descends on level-1 rejection would
never see it. Instead every level has its own reserved budget, and
**safety-critical strata are pre-registered primary hypotheses** with
dedicated alpha, tested individually and exempt from e-BH masking by
better-behaved siblings. The power test in the suite is exactly this case:
overall metric up ~0.3pp, pedestrian-night down 2pp — and the stratum node
must fire. Alpha propagation is top-down only in the *reporting* sense
(attribution descends into difficulty bands of regressed strata); decisions
never require a parent rejection.

## 9. Rare-class policy

* **Minimum counts**: every non-empty stratum gets ≥ 200 sampled objects
  (or all of N_h if smaller) — below that, even 10pp effects are undetectable
  and the honest answer is INSUFFICIENT_EVIDENCE, which the three-outcome
  logic produces naturally.
* **Oversampling with weight correction**: rare + risky strata get risk
  multipliers and floors (§4); HT weights keep population estimates unbiased.
* **Per-class thresholds**: the margin δ and alpha allocation are policy
  fields; safety primaries effectively run at tighter per-node alpha with far
  larger minimum samples. (A per-stratum δ_h map is a straightforward policy
  extension; v1 keeps a single δ for legibility.)
* **Escalation**: rare strata that end INSUFFICIENT but suspect consume their
  reserve up to the whole stratum. If a stratum is exhausted and still
  undecided, the ledger says so explicitly (stopping_reason
  `escalation_exhausted`) — the decision is a visible budget/detectability
  statement, not silence.

## 10. Sample sizes and power (with the design effect)

Fixed-n planning numbers for a Bernoulli recall metric at baseline p₀ = 0.9,
one-sided α = 0.05, power 0.9 ((z₀.₉₅+z₀.₉)² ≈ 8.57). Paired variance uses
Var(d) ≈ ν − Δ² with measured-typical discordance ν ≈ 0.04 + Δ:

| detectable Δ | unpaired, per model arm | paired, iid units | paired, DEFF = 2 | paired, DEFF = 2, anytime (~×1.35) |
|---:|---:|---:|---:|---:|
| 1pp  | 16,100 | 4,280 | 8,550 | ~11,500 |
| 2pp  |  4,150 | 1,280 | 2,550 |  ~3,450 |
| 5pp  |    745 |   300 |   600 |    ~810 |
| 10pp |    214 |   111 |   223 |    ~300 |

Notes: (i) pairing beats doubling the corpus — the unpaired column is per
*arm*; (ii) the anytime-valid overhead (~30–40% at these scales, shrinking
with the e-process's adaptive bets) buys the right to stop at any time, which
in expectation *saves* samples whenever the effect is larger than the MDE
(the 10pp test stops ~15× before budget); (iii) measured DEFF on paired
differences was ≈1.2, not 2 — the table's DEFF=2 column is the conservative
planning figure requested, the ledger reports the realized one. The default
safety floor (2,500) is the 2pp row with the realized design effect plus
head-room.

## 11. Compute-budget staging

Default policy on a 320k corpus: plan ≈ 25k objects, staged
`[15%, 40%, 100%]` of plan (≈ 3.8k → 10k → 25k) + escalation reserve, i.e. a
10K→50K→250K→full ladder scaled to plan size. Advancement rules: advance
while any node is undecided and budget remains; stop immediately on any
confirmed REGRESSION (sticky, anytime-valid) or when overall + all safety
primaries PASS with nothing undecided; escalate only suspect/safety strata;
terminal state otherwise INSUFFICIENT_EVIDENCE with per-stratum sample
accounting in the ledger.

## 12. Prediction caching

Baseline predictions are computed once per (dataset fingerprint, model
fingerprint) and reused across every candidate update
(`paired.PredictionCache`, disk + memory). Fingerprints cover everything
predictions depend on — dataset identity/seed/size, model version + effects
config — so **invalidation is by construction**: any change produces a new
key; nothing stale can ever be served. A compute counter proves in tests that
the second candidate evaluation performs zero baseline recomputation. In a
real deployment the fingerprint inputs become dataset content hash + model
checkpoint digest + inference config (thresholds, NMS, quantization), and the
cache stores per-unit predictions rather than a success vector.

## 13. Sampling-bias prevention

* The plan is built by a function that **cannot see candidate outcomes** (no
  such parameter exists), from population structure + a prior variance
  profile only, then persisted with seed, config and a content hash over the
  selected and reserve unit ids. The hash is recorded in every run's lineage;
  two candidates with opposite outcomes provably receive byte-identical plans
  (asserted in tests).
* Escalation draws from a reserve whose order was frozen at plan time, so
  even adaptive *budget* decisions never become adaptive *selection*.
* **If outcome-adaptive sampling is ever added** (e.g. active sampling of
  high-disagreement regions), estimates must be corrected with
  inverse-propensity weights w_i = 1/π_i under recorded, everywhere-positive
  selection probabilities π_i, and CS validity must be re-established for the
  weighted observations (bounded w_i·d_i, e.g. via truncated propensities).
  Without that correction, adaptive selection biases Δ̂ toward whatever the
  selector chases. v1 deliberately does not do adaptive selection.

## 14. Bayesian complement

Alongside every frequentist decision, `sequential.py` computes a Beta-Binomial
posterior P(Δ < −δ | data) from the discordant pairs (θ = P(regression pair |
discordant) with Beta(1,1) prior; Δ = ν(1−2θ)). Operational comparison:

* *For* the posterior: a single monotone number ("99.99% probable this is a
  real regression") that program managers read correctly without training;
  natural for a decision-theoretic cost tradeoff.
* *Against* (as the gate): posteriors have no frequentist stopping guarantee
  under continuous monitoring without committing to a prior + loss and
  accepting their audit burden; e-values give the same "accumulating
  evidence" narrative *with* an error guarantee any auditor can check
  (e ≥ 1/α), and they compose across strata (e-BH) and across looks by
  multiplication.

Verdict: **e-values gate, posterior explains.** Both are in the ledger.

## 15. Failure modes and mitigations

| Failure mode | Mitigation in this design |
|---|---|
| Peeking inflates type-I | everything anytime-valid; fixed-n intervals never gate (§6) |
| Correlated frames overstate n | cluster inference units; DEFF/n_eff in every record (§2) |
| Masking: overall improves, stratum regresses | reserved per-level alpha + pre-registered safety primaries (§8) |
| Multiplicity across strata | hierarchical budgets + e-BH within level (§8) |
| "No signal" read as "safe" | three-outcome logic; INSUFFICIENT carries "not proven equivalent" (§7) |
| Outcome-dependent sampling bias | frozen hashed plans, frozen reserves, no adaptive selection (§13) |
| Rare strata silently undecidable | floors, oversampling+weights, escalation, explicit exhaustion reason (§9) |
| Stale baseline predictions | fingerprint-keyed cache, invalidation by construction (§12) |
| Broken candidate export burns budget | sanity smoke stage aborts in ~500 units (§5) |
| Trivial-magnitude "significance" | practical margin δ in both null hypotheses (§1) |
| Irreproducible decisions | seeds + fingerprints + plan hash + config in lineage; end-to-end determinism asserted in tests |
| Metric doesn't decompose per object (mAP) | pre-registered checkpoint paired clustered bootstrap, not fake sequential (§3) |

## 16. Final recommendation (defaults shipped in `controller.DEFAULT_POLICY`)

Paired, cluster-unit, stratified evaluation against a frozen hybrid
Neyman/risk-floor plan; empirical-Bernstein confidence sequences + betting
e-processes with a 0.5pp practical margin; three-outcome decisions; hierarchical
alpha budget (30% overall / 30% safety primaries / 15% class / 15% stratum /
10% difficulty) with e-BH within levels; staged budget 15%→40%→100% of a ~25k
plan with targeted escalation; fingerprint-keyed baseline cache; Beta-Binomial
posterior as explanatory complement. CUSUM reserved for future production
drift monitoring, not release gating.

## 17. Worked example: planted 2pp pedestrian-night regression (real run)

Setup: population `320k` objects / 26,666 containers (megaeval synthetic,
seed 42). Baseline `model-v41`. Candidate `model-v42` with planted effects
{pedestrian|night: −2pp, global: +0.4pp} — the masking case. Default policy
(δ = 0.5pp, α = 0.05, plan target 24k). Ground truth measured on the full
corpus afterwards: overall Δ = **+0.28pp** (candidate looks *better*
headline-wise), pedestrian|night Δ = **−1.63pp** realized (N = 17,561,
baseline recall 0.801).

Run (deterministic, reproducible from lineage):

* Plan: 25,699 objects frozen across 18 strata; pedestrian|night allocated
  2,501 (floor-driven; weight w = 7.02) + 15,060-object reserve.
* Decision: **REGRESSION** at 26,705 objects total = **8.4% of the corpus**
  (~12× cheaper than a full paired evaluation), stopping reason
  `regression_confirmed`, during escalation of the suspect safety primary.
* Pedestrian|night node at stop: n = 3,507 objects in 1,195 clusters
  (ICC 0.065, DEFF 1.20, n_eff 2,923); Δ̂ = −1.94pp,
  95% CS [−3.97pp, −0.48pp]; discordant pairs n01 = 110 vs n10 = 42;
  log e-value **7.50** vs boundary log(1/α_safety) = **4.89**
  (e ≈ 1,808 ≥ 133); Beta-Binomial P(Δ < −δ | data) = 0.99999.
* Evidence trajectory (n, log e): (129, 1.5) → (1002, 1.6) → (2501, 2.8) →
  (3006, 4.9) → (3507, 7.5) — the dashboard plots exactly this against the
  4.89 boundary.
* Overall node: Δ̂ = +0.03pp, CS [−0.51pp, +0.38pp] — correctly *not*
  regressed and *not yet* provably equivalent; the hierarchy is what surfaced
  the stratum despite the healthy headline.
* Ledger: 28 node records, each with the full §15-proof field set; lineage
  carries plan hash, fingerprints, seed and the entire statistical config.

The same engine with a null candidate (no planted effect) across 24 Monte
Carlo repetitions produced **zero** false REGRESSION verdicts (α = 0.05), and
with a planted 10pp effect stopped at ~2.8k samples — 11% of plan, <1% of the
corpus (both asserted in `tests/test_seqeval/`).