peeksafe 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/LICENSE +190 -0
  3. package/NOTICE +78 -0
  4. package/README.md +492 -2
  5. package/RESEARCH-NOTES.md +274 -0
  6. package/dist/baseline.d.ts +45 -0
  7. package/dist/baseline.d.ts.map +1 -0
  8. package/dist/baseline.js +49 -0
  9. package/dist/baseline.js.map +1 -0
  10. package/dist/check.d.ts +64 -0
  11. package/dist/check.d.ts.map +1 -0
  12. package/dist/check.js +172 -0
  13. package/dist/check.js.map +1 -0
  14. package/dist/cluster.d.ts +328 -0
  15. package/dist/cluster.d.ts.map +1 -0
  16. package/dist/cluster.js +598 -0
  17. package/dist/cluster.js.map +1 -0
  18. package/dist/errors.d.ts +87 -0
  19. package/dist/errors.d.ts.map +1 -0
  20. package/dist/errors.js +143 -0
  21. package/dist/errors.js.map +1 -0
  22. package/dist/evidence.d.ts +15 -0
  23. package/dist/evidence.d.ts.map +1 -0
  24. package/dist/evidence.js +30 -0
  25. package/dist/evidence.js.map +1 -0
  26. package/dist/frontier.d.ts +269 -0
  27. package/dist/frontier.d.ts.map +1 -0
  28. package/dist/frontier.js +460 -0
  29. package/dist/frontier.js.map +1 -0
  30. package/dist/gate.d.ts +173 -0
  31. package/dist/gate.d.ts.map +1 -0
  32. package/dist/gate.js +196 -0
  33. package/dist/gate.js.map +1 -0
  34. package/dist/index.d.ts +60 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +79 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/plan.d.ts +248 -0
  39. package/dist/plan.d.ts.map +1 -0
  40. package/dist/plan.js +482 -0
  41. package/dist/plan.js.map +1 -0
  42. package/dist/rand.d.ts +17 -0
  43. package/dist/rand.d.ts.map +1 -0
  44. package/dist/rand.js +92 -0
  45. package/dist/rand.js.map +1 -0
  46. package/dist/stats.d.ts +397 -0
  47. package/dist/stats.d.ts.map +1 -0
  48. package/dist/stats.js +1245 -0
  49. package/dist/stats.js.map +1 -0
  50. package/dist/stop.d.ts +163 -0
  51. package/dist/stop.d.ts.map +1 -0
  52. package/dist/stop.js +262 -0
  53. package/dist/stop.js.map +1 -0
  54. package/package.json +64 -4
  55. package/src/baseline.ts +72 -0
  56. package/src/check.ts +238 -0
  57. package/src/cluster.ts +830 -0
  58. package/src/errors.ts +208 -0
  59. package/src/evidence.ts +62 -0
  60. package/src/frontier.ts +782 -0
  61. package/src/gate.ts +358 -0
  62. package/src/index.ts +206 -0
  63. package/src/plan.ts +778 -0
  64. package/src/rand.ts +121 -0
  65. package/src/stats.ts +1579 -0
  66. package/src/stop.ts +452 -0
@@ -0,0 +1,274 @@
1
+ # What is new here, and what is not
2
+
3
+ peeksafe started life with four claims about being novel. This document is the adversarial
4
+ literature check that was run against them, and the record of what it cost: **two of the four did not
5
+ survive, one is a corollary of a textbook result, and one was not found on a search weak enough that
6
+ its absence proves little.**
7
+
8
+ It is kept in the repository, rather than quietly deleted once the README was corrected, for two
9
+ reasons. A reader deciding whether to trust a statistics library should be able to see where its
10
+ author was wrong. And the searches are written down, so anyone who thinks a verdict is too harsh,
11
+ or too generous, can check the work instead of taking it on faith.
12
+
13
+ **The measurements were never in question and none of them changed.** What changed is the sentence
14
+ above them.
15
+
16
+ ## The short version
17
+
18
+ | Claim | Where it lives | Verdict |
19
+ | --- | --- | --- |
20
+ | Sequential testing plus BH is invalid, twice over | `gate`, `bhCorrect`, `ebhCorrect` | **Known.** Taught as a named tutorial section since 2019 |
21
+ | A cheap proxy screen's accuracy does not transfer | the `proxy` screen in `computeFrontier` | **Known since 1978.** It is spectrum bias |
22
+ | The two-sample e-value has an evidence ceiling | `evidenceCeilingLogE`, `samplesForEvidence` | **Partially known.** A corollary of Laplace asymptotics; the closed form was not found |
23
+ | The certifiable-within-budget frontier | `computeFrontier`, `enumerateFrontier` | **Not found**, on three searches, which is weak evidence for an engineering artifact |
24
+
25
+ ---
26
+
27
+ ## 1 · Sequential testing plus Benjamini-Hochberg
28
+
29
+ **Verdict: known, both halves, and the more prominent half is a titled section in a widely-taught
30
+ tutorial. This was the headline claim and it does not survive as stated.**
31
+
32
+ The construction peeksafe exists to replace is: run each case until it looks decided, take a p-value
33
+ at the stopping boundary, then correct across cases with Benjamini-Hochberg.
34
+
35
+ **Half one: peeking invalidates the p-value.** Known since Armitage, McPherson and Rowe (1969), and
36
+ thoroughly re-established in the industrial A/B testing literature, where it is called peeking and is
37
+ the motivating problem for always-valid inference. Ramdas's KDD 2019 tutorial states it directly:
38
+ "repeatedly running batch tests on accumulating data invalidates the p-value and inflates type-1
39
+ error".
40
+
41
+ **Half two: BH cannot consume those p-values.** This is not merely implied in that literature. It is
42
+ a titled section of the same tutorial: **"Why Benjamini-Hochberg cannot be used online (5 mins)"**.
43
+ The tutorial further warns that "ignoring the interplay with the outer sequential process could
44
+ unknowingly inflate the number of false discoveries", which was this library's headline conclusion,
45
+ published in 2019.
46
+
47
+ The entire e-BH line of work exists because of this. Wang and Ramdas (2022) introduced e-BH so FDR
48
+ control survives arbitrary dependence and sequential evidence, and the follow-up on stopped e-BH
49
+ studies exactly what happens when e-processes are stopped adaptively and multiplicity is corrected
50
+ afterwards. A reader who knows that literature reads the original framing as a restatement of its
51
+ premise.
52
+
53
+ **A third part, also known.** Testing against a historical rate treated as if it were known inflates
54
+ type I error, and the standard remedy is to carry the reference arm's uncertainty into the null
55
+ rather than plugging in a point estimate. That is standard in the external-control and historical-
56
+ borrowing literature, and `gate`'s use of the baseline posterior as the null is that standard remedy
57
+ correctly applied, not a discovery.
58
+
59
+ ### What genuinely survives
60
+
61
+ One observation, and it is worth keeping:
62
+
63
+ > BH's own conservativeness at large `m` masks the per-case invalidity, so the naive construction
64
+ > produces visible false discoveries **more often at `m = 10` than at `m = 200`**, the opposite of
65
+ > where a practitioner would look for it, and `m = 10` is the regime cost economics push you toward.
66
+
67
+ That was not found stated anywhere. It is a quantitative nuance on a known defect rather than a new
68
+ defect, and it is genuinely counterintuitive and practically useful. The measurement itself (4.6×
69
+ per-case inflation on an eval suite, with the same underlying draws analysed all three ways) is also
70
+ a contribution, because measurements on a new domain have value even when the theory is old.
71
+
72
+ This is what the README's section "The part that is genuinely counterintuitive" now leads with, and
73
+ the rest is presented as implementing a known-correct construction rather than discovering a defect.
74
+
75
+ ### Citations
76
+
77
+ - Ramdas, *Fundamentals of large-scale sequential experimentation*, KDD 2019 tutorial, including the
78
+ section "Why Benjamini-Hochberg cannot be used online": https://stat.cmu.edu/~aramdas/kdd19/
79
+ - Wang and Ramdas, *False discovery rate control with e-values*, 2022.
80
+ - Wang, Dandapanthula and Ramdas, *Anytime-valid FDR control with the stopped e-BH procedure*, 2025:
81
+ https://arxiv.org/abs/2502.08539
82
+ - Grünwald, de Heide and Koolen, *Safe Testing*: https://arxiv.org/abs/1906.07801
83
+ - Principled type I error rate inflation in two-arm designs with external control borrowing:
84
+ https://arxiv.org/html/2508.16348
85
+ - Armitage, McPherson and Rowe, *Repeated significance tests on accumulating data*, JRSS-A 1969.
86
+
87
+ ### Searches run
88
+
89
+ Five. Optional stopping invalidating p-values with BH and FDR; anytime-valid FDR and stopped e-BH;
90
+ external control type I error inflation with a plug-in null; e-value safe testing with informative
91
+ nulls; and a direct fetch of the tutorial page, because a search result had already named the
92
+ section. The confirming step was the fetch, not a search.
93
+
94
+ ---
95
+
96
+ ## 2 · The proxy screen that does not transfer
97
+
98
+ **Verdict: known. It has a name and a 1978 citation.**
99
+
100
+ `computeFrontier` offers three screens (`none`, `expensive`, and `proxy`) and prices the `proxy`
101
+ one by attenuating its effect with Youden's J. The claim under check was that a cheap screen's
102
+ accuracy measured on a whole suite does not transfer to the narrow population that actually decides
103
+ anything.
104
+
105
+ The phenomenon is **spectrum bias**, first reported by Ransohoff and Feinstein in 1978. Sensitivity
106
+ and specificity, and therefore Youden's J, differ between subgroups with different severity or
107
+ features, so a test's measured accuracy on a broad population does not carry to a narrow one. The
108
+ modern literature refines this into a spectrum *effect* (accuracy differs by subgroup) becoming a
109
+ spectrum *bias* (the difference changes the likelihood ratios, and therefore the decisions).
110
+
111
+ A screen with J of 0.67 over a whole suite and J of 0.00 over the handful of genuinely regressed
112
+ cases is that, exactly. The consequence for this library is a modelling constraint rather than a
113
+ result: a J measured suite-wide is the wrong number to attenuate the frontier with, and
114
+ `computeFrontier` therefore takes screen recall as a caller-supplied parameter rather than inferring
115
+ one.
116
+
117
+ ### What would add value, and is not in this package
118
+
119
+ Two things, and both are about demonstration rather than the phenomenon: a controlled design that
120
+ holds the expensive labels fixed and varies only the text of a failing answer, and the distinction
121
+ between a proxy that is *noisy* and one that is *structurally blind* to a failure mode, the latter
122
+ explaining why no number of runs helps. Neither of those experiments ships here. peeksafe models the
123
+ screen; it does not measure one for you.
124
+
125
+ ### Citations
126
+
127
+ - Ransohoff and Feinstein, *Problems of spectrum and bias in evaluating the efficacy of diagnostic
128
+ tests*, NEJM 1978: https://pubmed.ncbi.nlm.nih.gov/692598/
129
+ - Spectrum bias, Catalog of Bias: https://catalogofbias.org/biases/spectrum-bias/
130
+ - *Distinguishing spectrum effects from spectrum biases*, BMC Medical Research Methodology 2008:
131
+ https://link.springer.com/article/10.1186/1471-2288-8-7
132
+ - *Spectrum bias: why clinicians need to be cautious when applying diagnostic test studies*, Family
133
+ Practice: https://academic.oup.com/fampra/article/25/5/390/443697
134
+
135
+ ### Searches run
136
+
137
+ One, which was decisive. The phenomenon is named and the first search returned the originating
138
+ citation, so further searching would only have confirmed it.
139
+
140
+ ---
141
+
142
+ ## 3 · The evidence ceiling
143
+
144
+ **Verdict: partially known. The mathematics is a corollary of a textbook result. The closed form, the
145
+ naming, and the planning inversion were not found.**
146
+
147
+ `twoSampleLogE` has a finite limit as candidate runs go to infinity, and `evidenceCeilingLogE`
148
+ computes it. The claim under check was that this ceiling is a new observation.
149
+
150
+ ### What is established
151
+
152
+ The asymptotic expansion behind the derivation is the standard Laplace approximation to a marginal
153
+ likelihood, stated in Kass and Raftery's review as
154
+
155
+ ```
156
+ log m(D) = log P(D | θ̂, H) − (d/2) log n + O(1)
157
+ ```
158
+
159
+ With two hypotheses that share a likelihood and a parameter dimension, the first two terms are
160
+ identical and cancel, leaving the `O(1)` term: the ratio of prior densities at the maximum. So a
161
+ bounded Bayes factor in this situation is not a surprise; it is what the standard expansion says must
162
+ happen.
163
+
164
+ The complementary fact is equally standard and is stated the other way round in the Bayes factor
165
+ design literature: a Bayes factor is consistent when the alternative prior is continuous and positive
166
+ at the true value and the hypotheses are genuinely different distributions. The remark that "one
167
+ expects a Bayes factor to grow without bound in the data, and it does when the hypotheses are
168
+ different distributions" is a restatement of that known consistency condition, and should be read as
169
+ citing it rather than naming a failure of intuition nobody had noticed.
170
+
171
+ The practical consequence is also established, in a different vocabulary. In clinical trials the
172
+ information obtainable when testing against a historical or external control is capped by that
173
+ control's size, quantified as prior effective sample size with explicit maximal borrowing caps. That
174
+ is the same statement as "the ceiling grows linearly in baseline runs", written for a trial-design
175
+ audience.
176
+
177
+ ### What was not found
178
+
179
+ - The phrase "evidence ceiling" as a named concept.
180
+ - The explicit closed form for the two-sample beta-binomial case,
181
+ `ceiling(n_b) ≈ n_b · KL(p̄_b ‖ p) + log f_alt(p) + ½ log(2π p̄_b(1−p̄_b)/n_b)`.
182
+ - The KL direction being called out as load bearing, with the 18% error from reversing it.
183
+ - The planning inversion `n_b ≳ log(m/q) / KL(p̄_b ‖ p̄_b − δ)`, and the observation that no planner
184
+ prints it.
185
+
186
+ Those four are the contribution, and `samplesForEvidence` is the last of them made executable. They
187
+ are engineering artifacts derived from known asymptotics, which is worth publishing, but it is a
188
+ different claim from discovering the ceiling.
189
+
190
+ ### Citations
191
+
192
+ - Kass and Raftery, *Bayes Factors*, JASA 1995:
193
+ https://sites.stat.washington.edu/raftery/Research/PDF/weakliem1999.pdf
194
+ - Laplace approximation and Bayesian asymptotics, the expansion in standard form:
195
+ https://www.stats.ox.ac.uk/~steffen/teaching/bs2HT9/bayes.pdf
196
+ - Prior effective sample size when borrowing, Zhang et al., *Statistics in Medicine* 2025:
197
+ https://onlinelibrary.wiley.com/doi/10.1002/sim.70235
198
+ - Scoping review of borrowing quantification, including maximal borrowing caps:
199
+ https://arxiv.org/abs/2605.27184
200
+ - Bayes factor consistency conditions, closed-form power and sample size:
201
+ https://arxiv.org/pdf/2406.19940
202
+
203
+ ### Searches run
204
+
205
+ Five. Bayes factor bounded, converging to a prior density ratio under Laplace; Bayes factor with two
206
+ priors on the same parameter; e-value safe testing with bounded evidence against a historical control
207
+ under an informative null; Kass and Raftery same-dimension cancellation; and the phrase "evidence
208
+ ceiling" itself, which returned nothing using that term.
209
+
210
+ ---
211
+
212
+ ## 4 · The certifiable-within-budget frontier
213
+
214
+ **Verdict: not found, on three searches. That is weak evidence, and adjacent work exists.**
215
+
216
+ `computeFrontier` and `enumerateFrontier` produce a Pareto region of configurations that can be
217
+ certified within a budget, with the amortisation window as a first-class axis. Neither the frontier
218
+ as an object, nor the finding that the amortisation window rather than variance decides between
219
+ paired and unpaired designs, was found.
220
+
221
+ Two caveats a reader should weigh:
222
+
223
+ 1. **This is an engineering and economics artifact, not a theorem.** Work of this kind lives in
224
+ company engineering blogs and internal design documents far more often than in the literature, so
225
+ absence from academic search is weaker evidence here than for the claims above.
226
+ 2. **Adjacent work exists.** Casting benchmarking as finite-population inference under a fixed query
227
+ budget is an active topic, seeking tight confidence intervals for model accuracy with valid
228
+ coverage under a budget constraint. That is a different object (accuracy intervals under a query
229
+ budget, rather than a Pareto region of certifiable configurations with an amortisation axis), but
230
+ it is close enough to belong in related work rather than be left out.
231
+
232
+ The classical paired-versus-unpaired decision is made on correlation and variance reduction. That the
233
+ amortisation window displaces that criterion is, as far as these searches went, this library's own.
234
+ It is stated as "we have not found this drawn anywhere" rather than as established novelty, and the
235
+ distinction matters.
236
+
237
+ ### Citations
238
+
239
+ - *Efficient Evaluation of LLM Performance with Statistical Guarantees*:
240
+ https://arxiv.org/html/2601.20251v1
241
+ - Matched pairs design, standard exposition of the classical criterion:
242
+ https://www.growthbook.io/insights/matched-pairs-design-explained-definition-benefits
243
+
244
+ ### Searches run
245
+
246
+ Three. Paired versus unpaired decided by control-arm cost and amortisation; LLM evaluation sample
247
+ size and budget for regression detection in CI; and the historical-control angle from claim 3's
248
+ searches, which surfaced the trial-design trade-offs but not a frontier.
249
+
250
+ ---
251
+
252
+ ## What this changed in the README
253
+
254
+ Three sentences, all narrowing:
255
+
256
+ 1. The opening no longer says the sequential-plus-BH result is not written down anywhere. It says
257
+ outright that neither defect is a new observation, names the tutorial section, and states that
258
+ what the library contributes is the working construction plus a measurement of what the broken one
259
+ costs.
260
+ 2. The evidence ceiling is introduced as a corollary of the standard Laplace expansion, with the
261
+ historical-borrowing cap named, before the closed form and the planning rule are claimed.
262
+ 3. "As far as we can tell" is gone from the two places it could not be supported.
263
+
264
+ What did **not** change: every number. The 4.6× per-case inflation, the 13-against-0 false
265
+ discoveries, the ceiling ladder, the `log(m/q) / KL` planning rule against its exact answer, and the
266
+ frontier cells. Prior art does not touch a measurement, and `test/paper.test.ts` still reproduces all
267
+ of them from seeded draws with `pnpm test`.
268
+
269
+ ## Standing invitation
270
+
271
+ If a verdict here is wrong in either direction (a claim marked known that isn't, or one marked "not
272
+ found" that is sitting in a paper somewhere), the citation is worth more than the claim. Open an
273
+ issue. The searches above are written down precisely so that this is checkable rather than a matter
274
+ of trust.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * baseline.ts: what a case's reference revision did, and the null it implies.
3
+ *
4
+ * peeksafe never runs anything. A caller brings counts, and these are the shapes
5
+ * those counts arrive in.
6
+ *
7
+ * The distinction that matters is between a baseline that says "this case passed
8
+ * 51 of 60 times" and no baseline at all. The prototype this library came from
9
+ * treated a missing baseline as `0/60`, whose Beta(1,1) posterior median is 0.5,
10
+ * and cheerfully gated against a null of "this case passes half the time". It
11
+ * reported a verdict of PASS and a suite improvement of +87.5 points, measured
12
+ * against nothing. Every entry point here therefore takes `BaselineStat |
13
+ * undefined` explicitly and refuses to invent one.
14
+ */
15
+ /** One case's recorded reference performance. */
16
+ export interface BaselineStat {
17
+ caseId: string;
18
+ successes: number;
19
+ trials: number;
20
+ }
21
+ /**
22
+ * The minimum a case has to declare to be planned or gated. `suite` groups
23
+ * cases into families for the cluster-robust suite summary; leave it undefined
24
+ * and every case is its own family.
25
+ */
26
+ export interface CaseRef {
27
+ id: string;
28
+ suite?: string;
29
+ }
30
+ /**
31
+ * The rate the null is tested at.
32
+ *
33
+ * Not `successes / trials`. A 60-run baseline at a true rate of 0.85 has a
34
+ * standard error of 0.046, which is a third of a 15-point effect, so testing
35
+ * against the point estimate treats a noisy number as an oracle. When the
36
+ * baseline happens to have come in high, every candidate looks worse than it is.
37
+ * Taking a low quantile of the baseline's own posterior instead is what stops
38
+ * that, at the cost of some power.
39
+ *
40
+ * `q` is that quantile. Lower is more conservative.
41
+ */
42
+ export declare function baselineNullRate(b: BaselineStat, q?: number): number;
43
+ /** Index a list of baselines by case id, rejecting duplicates rather than merging them. */
44
+ export declare function toBaselineMap(stats: readonly BaselineStat[]): Map<string, BaselineStat>;
45
+ //# sourceMappingURL=baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,iDAAiD;AACjD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,SAAO,GAAG,MAAM,CAIlE;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAiBvF"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * baseline.ts: what a case's reference revision did, and the null it implies.
3
+ *
4
+ * peeksafe never runs anything. A caller brings counts, and these are the shapes
5
+ * those counts arrive in.
6
+ *
7
+ * The distinction that matters is between a baseline that says "this case passed
8
+ * 51 of 60 times" and no baseline at all. The prototype this library came from
9
+ * treated a missing baseline as `0/60`, whose Beta(1,1) posterior median is 0.5,
10
+ * and cheerfully gated against a null of "this case passes half the time". It
11
+ * reported a verdict of PASS and a suite improvement of +87.5 points, measured
12
+ * against nothing. Every entry point here therefore takes `BaselineStat |
13
+ * undefined` explicitly and refuses to invent one.
14
+ */
15
+ import { PeeksafeError, requireCounts, requireOpenProbability } from "./errors.js";
16
+ import { betaQuantile } from "./stats.js";
17
+ /**
18
+ * The rate the null is tested at.
19
+ *
20
+ * Not `successes / trials`. A 60-run baseline at a true rate of 0.85 has a
21
+ * standard error of 0.046, which is a third of a 15-point effect, so testing
22
+ * against the point estimate treats a noisy number as an oracle. When the
23
+ * baseline happens to have come in high, every candidate looks worse than it is.
24
+ * Taking a low quantile of the baseline's own posterior instead is what stops
25
+ * that, at the cost of some power.
26
+ *
27
+ * `q` is that quantile. Lower is more conservative.
28
+ */
29
+ export function baselineNullRate(b, q = 0.25) {
30
+ requireCounts(b.successes, b.trials, "baselineNullRate");
31
+ requireOpenProbability(q, "q", "baselineNullRate");
32
+ return betaQuantile(1 + b.successes, 1 + b.trials - b.successes, q);
33
+ }
34
+ /** Index a list of baselines by case id, rejecting duplicates rather than merging them. */
35
+ export function toBaselineMap(stats) {
36
+ const m = new Map();
37
+ for (const b of stats) {
38
+ if (m.has(b.caseId)) {
39
+ throw new PeeksafeError("PEEKSAFE_E_CASE_DUPLICATE", `toBaselineMap: duplicate baseline for case ${b.caseId}`, {
40
+ detail: { caseId: b.caseId },
41
+ hint: "merging two baselines for one case would count its runs twice; dedupe them before calling",
42
+ });
43
+ }
44
+ requireCounts(b.successes, b.trials, `toBaselineMap(${b.caseId})`);
45
+ m.set(b.caseId, b);
46
+ }
47
+ return m;
48
+ }
49
+ //# sourceMappingURL=baseline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.js","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAmB1C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAe,EAAE,CAAC,GAAG,IAAI;IACxD,aAAa,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACzD,sBAAsB,CAAC,CAAC,EAAE,GAAG,EAAE,kBAAkB,CAAC,CAAC;IACnD,OAAO,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;AACtE,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,aAAa,CAAC,KAA8B;IAC1D,MAAM,CAAC,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC1C,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,aAAa,CACrB,2BAA2B,EAC3B,8CAA8C,CAAC,CAAC,MAAM,EAAE,EACxD;gBACE,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;gBAC5B,IAAI,EAAE,2FAA2F;aAClG,CACF,CAAC;QACJ,CAAC;QACD,aAAa,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QACnE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * check.ts: the error rate and the power the gate delivers at rates you name.
3
+ *
4
+ * The two-sample e-value is not an e-value at every fixed rate (README, "What
5
+ * that guarantee rests on"), so its error control is computed rather than
6
+ * assumed. This is that computation, for a caller whose rates, baseline sizes
7
+ * or options sit outside the grid the tests check. Moving the candidate below
8
+ * the baseline turns the same computation into the power.
9
+ */
10
+ import { type Evidence } from "./evidence.js";
11
+ export interface TypeOneErrorOptions {
12
+ /** Shared true pass rate of the baseline and the candidate. */
13
+ rate: number;
14
+ /** Baseline runs the case will be gated against. */
15
+ baselineTrials: number;
16
+ /** The bar is `1 / alpha`. For `gate`'s solo bar pass `fdr / suiteSize`. */
17
+ alpha: number;
18
+ /** The most candidate runs any case could reach, e.g. `shouldStop`'s `maxTrials`. */
19
+ horizon: number;
20
+ mde?: number;
21
+ altConcentration?: number;
22
+ /** Which e-value `gate` will use. */
23
+ evidence?: Evidence;
24
+ }
25
+ export interface CertifyProbabilityOptions extends Omit<TypeOneErrorOptions, "rate"> {
26
+ /** True pass rate of the baseline revision. */
27
+ baselineRate: number;
28
+ /** True pass rate of the candidate revision. */
29
+ candidateRate: number;
30
+ }
31
+ /**
32
+ * Exact probability that a case is certified as regressed within `horizon`
33
+ * candidate runs, however it was stopped. With the candidate below the baseline
34
+ * this is the power; at or above it, the type I error.
35
+ *
36
+ * Exact because the e-value depends on the candidate only through its counts,
37
+ * so a dynamic program over the success count replaces simulation. A 240-run
38
+ * baseline at a horizon of 600 takes tens of milliseconds.
39
+ */
40
+ export declare function certifyProbability(options: CertifyProbabilityOptions): number;
41
+ /**
42
+ * Exact probability that a case which did not move is ever certified within
43
+ * `horizon` candidate runs, however it was stopped. At or below `alpha` means
44
+ * the gate keeps its promise for this case.
45
+ */
46
+ export declare function typeOneError(options: TypeOneErrorOptions): number;
47
+ export interface PairedCertifyProbabilityOptions {
48
+ /** True pass rate of the baseline revision. */
49
+ baselineRate: number;
50
+ /** True pass rate of the candidate revision. */
51
+ candidateRate: number;
52
+ /** Share of pairs whose two runs see the same random draw (a shared seed); 0 for independent runs. */
53
+ coupling?: number;
54
+ /** The bar is `1 / alpha`. For `gate`'s solo bar pass `fdr / suiteSize`. */
55
+ alpha: number;
56
+ /** The most pairs any case could reach. */
57
+ horizon: number;
58
+ }
59
+ /**
60
+ * `certifyProbability` for a paired case: the exact probability it is certified within
61
+ * `horizon` pairs, however it was stopped. The power when the candidate is lower.
62
+ */
63
+ export declare function pairedCertifyProbability(options: PairedCertifyProbabilityOptions): number;
64
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,OAAO,EAAE,KAAK,QAAQ,EAAmB,MAAM,eAAe,CAAC;AAI/D,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,cAAc,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qCAAqC;IACrC,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,mBAAmB,EAAE,MAAM,CAAC;IAClF,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,MAAM,CA6E7E;AA8BD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAIjE;AAED,MAAM,WAAW,+BAA+B;IAC9C,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,aAAa,EAAE,MAAM,CAAC;IACtB,sGAAsG;IACtG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,+BAA+B,GAAG,MAAM,CAmDzF"}
package/dist/check.js ADDED
@@ -0,0 +1,172 @@
1
+ /**
2
+ * check.ts: the error rate and the power the gate delivers at rates you name.
3
+ *
4
+ * The two-sample e-value is not an e-value at every fixed rate (README, "What
5
+ * that guarantee rests on"), so its error control is computed rather than
6
+ * assumed. This is that computation, for a caller whose rates, baseline sizes
7
+ * or options sit outside the grid the tests check. Moving the candidate below
8
+ * the baseline turns the same computation into the power.
9
+ */
10
+ import { PeeksafeError, requireOpenProbability, requirePositiveConfig, requireProbability, } from "./errors.js";
11
+ import { requireEvidence } from "./evidence.js";
12
+ import { cannotDropBy, DEFAULT_GATE_OPTIONS } from "./gate.js";
13
+ import { logBeta, logGamma, pairedLogE, twoSamplePriors } from "./stats.js";
14
+ /**
15
+ * Exact probability that a case is certified as regressed within `horizon`
16
+ * candidate runs, however it was stopped. With the candidate below the baseline
17
+ * this is the power; at or above it, the type I error.
18
+ *
19
+ * Exact because the e-value depends on the candidate only through its counts,
20
+ * so a dynamic program over the success count replaces simulation. A 240-run
21
+ * baseline at a horizon of 600 takes tens of milliseconds.
22
+ */
23
+ export function certifyProbability(options) {
24
+ const { baselineRate, candidateRate: pc, baselineTrials: nb, alpha, horizon } = options;
25
+ const mde = options.mde ?? DEFAULT_GATE_OPTIONS.mde;
26
+ const concentration = options.altConcentration ?? DEFAULT_GATE_OPTIONS.altConcentration;
27
+ const evidence = options.evidence ?? DEFAULT_GATE_OPTIONS.evidence;
28
+ requireEvidence(evidence, "certifyProbability");
29
+ requireOpenProbability(baselineRate, "baselineRate", "certifyProbability");
30
+ requireOpenProbability(pc, "candidateRate", "certifyProbability");
31
+ requireOpenProbability(alpha, "alpha", "certifyProbability");
32
+ requireOpenProbability(mde, "mde", "certifyProbability");
33
+ requirePositiveConfig(concentration, "altConcentration", "certifyProbability");
34
+ for (const [name, v] of [
35
+ ["baselineTrials", nb],
36
+ ["horizon", horizon],
37
+ ]) {
38
+ if (!Number.isInteger(v) || v < 1) {
39
+ throw new PeeksafeError("PEEKSAFE_E_CONFIG", `certifyProbability: ${name} must be a positive integer, got ${v}`, {
40
+ detail: { [name]: v },
41
+ });
42
+ }
43
+ }
44
+ const bar = Math.log(1 / alpha);
45
+ const logChoose = (n, k) => logGamma(n + 1) - logGamma(k + 1) - logGamma(n - k + 1);
46
+ let total = 0;
47
+ for (let sb = 0; sb <= nb; sb++) {
48
+ const weight = Math.exp(logChoose(nb, sb) + sb * Math.log(baselineRate) + (nb - sb) * Math.log(1 - baselineRate));
49
+ if (weight < 1e-12 || cannotDropBy({ successes: sb, trials: nb }, mde))
50
+ continue;
51
+ const { nullPrior: n0, altPrior: a1 } = twoSamplePriors(sb, nb, mde, concentration);
52
+ let alive = new Float64Array(horizon + 1);
53
+ alive[0] = 1;
54
+ let crossed = 0;
55
+ // Only the band of success counts still carrying mass is stepped.
56
+ let lo = 0;
57
+ let hi = 0;
58
+ for (let n = 1; n <= horizon; n++) {
59
+ const next = new Float64Array(horizon + 1);
60
+ for (let s = lo; s <= hi; s++) {
61
+ next[s + 1] += alive[s] * pc;
62
+ next[s] += alive[s] * (1 - pc);
63
+ }
64
+ hi++;
65
+ // Every count in the band is tested: logE is not monotone in s, so the
66
+ // crossing set need not be a prefix. Successive counts differ by a ratio
67
+ // of Beta functions, which keeps this cheap.
68
+ let logE = rawLogE(lo, n, sb, nb, n0, a1);
69
+ for (let s = lo; s <= hi; s++) {
70
+ if (s > lo) {
71
+ const f = n - s + 1;
72
+ logE +=
73
+ Math.log((a1.a + s - 1) / (a1.b + f - 1)) - Math.log((n0.a + s - 1) / (n0.b + f - 1));
74
+ }
75
+ const decided = evidence === "universal"
76
+ ? universalFromBayes(logE, s, n, sb, nb)
77
+ : s / n >= sb / nb
78
+ ? Math.min(0, logE)
79
+ : logE;
80
+ if (decided >= bar) {
81
+ crossed += next[s];
82
+ next[s] = 0;
83
+ }
84
+ }
85
+ while (lo < hi && next[lo] < 1e-18)
86
+ lo++;
87
+ while (hi > lo && next[hi] < 1e-18)
88
+ hi--;
89
+ alive = next;
90
+ }
91
+ total += weight * crossed;
92
+ }
93
+ return total;
94
+ }
95
+ const xlogy = (x, y) => (x === 0 ? 0 : x * Math.log(y));
96
+ /** The uncapped Bayes factor, which both statistics are built from. */
97
+ const rawLogE = (s, n, _sb, _nb, n0, a1) => logBeta(a1.a + s, a1.b + n - s) -
98
+ logBeta(a1.a, a1.b) -
99
+ (logBeta(n0.a + s, n0.b + n - s) - logBeta(n0.a, n0.b));
100
+ /** `universalTwoSampleLogE`, from the uncapped Bayes factor: times the pooled mixture over the null maximum. */
101
+ function universalFromBayes(rawLog, s, n, sb, nb) {
102
+ const S = sb + s;
103
+ const N = nb + n;
104
+ const pb = sb / nb;
105
+ const pc = s / n;
106
+ const sup = pc >= pb
107
+ ? xlogy(sb, pb) + xlogy(nb - sb, 1 - pb) + xlogy(s, pc) + xlogy(n - s, 1 - pc)
108
+ : xlogy(S, S / N) + xlogy(N - S, 1 - S / N);
109
+ return rawLog + logBeta(1 + S, 1 + N - S) - sup;
110
+ }
111
+ /**
112
+ * Exact probability that a case which did not move is ever certified within
113
+ * `horizon` candidate runs, however it was stopped. At or below `alpha` means
114
+ * the gate keeps its promise for this case.
115
+ */
116
+ export function typeOneError(options) {
117
+ const { rate, ...rest } = options;
118
+ requireOpenProbability(rate, "rate", "typeOneError");
119
+ return certifyProbability({ ...rest, baselineRate: rate, candidateRate: rate });
120
+ }
121
+ /**
122
+ * `certifyProbability` for a paired case: the exact probability it is certified within
123
+ * `horizon` pairs, however it was stopped. The power when the candidate is lower.
124
+ */
125
+ export function pairedCertifyProbability(options) {
126
+ const { baselineRate: pb, candidateRate: pc, alpha, horizon } = options;
127
+ const rho = options.coupling ?? 0;
128
+ requireOpenProbability(pb, "baselineRate", "pairedCertifyProbability");
129
+ requireOpenProbability(pc, "candidateRate", "pairedCertifyProbability");
130
+ requireOpenProbability(alpha, "alpha", "pairedCertifyProbability");
131
+ requireProbability(rho, "coupling", "pairedCertifyProbability");
132
+ if (!Number.isInteger(horizon) || horizon < 1) {
133
+ throw new PeeksafeError("PEEKSAFE_E_CONFIG", `pairedCertifyProbability: horizon must be a positive integer, got ${horizon}`, {
134
+ detail: { horizon },
135
+ });
136
+ }
137
+ const worse = rho * Math.max(0, pb - pc) + (1 - rho) * pb * (1 - pc);
138
+ const better = rho * Math.max(0, pc - pb) + (1 - rho) * pc * (1 - pb);
139
+ const bar = Math.log(1 / alpha);
140
+ // The e-value depends on (worse, discordant) only, so crossing is tabulated once per state.
141
+ const crosses = Array.from({ length: horizon + 1 }, (_, d) => Uint8Array.from({ length: d + 1 }, (_, w) => (pairedLogE(w, d) >= bar ? 1 : 0)));
142
+ // alive[d][w]: probability of d discordant pairs, w of them worse, and no crossing yet.
143
+ let alive = [Float64Array.of(1)];
144
+ let crossed = 0;
145
+ for (let n = 1; n <= horizon; n++) {
146
+ const next = Array.from({ length: n + 1 }, (_, d) => new Float64Array(d + 1));
147
+ for (let d = 0; d < n; d++) {
148
+ const row = alive[d];
149
+ for (let w = 0; w <= d; w++) {
150
+ const m = row[w];
151
+ if (m < 1e-300)
152
+ continue;
153
+ next[d][w] += m * (1 - worse - better);
154
+ next[d + 1][w + 1] += m * worse;
155
+ next[d + 1][w] += m * better;
156
+ }
157
+ }
158
+ for (let d = 1; d <= n; d++) {
159
+ const row = next[d];
160
+ const cross = crosses[d];
161
+ for (let w = 0; w <= d; w++) {
162
+ if (cross[w] && row[w] > 0) {
163
+ crossed += row[w];
164
+ row[w] = 0;
165
+ }
166
+ }
167
+ }
168
+ alive = next;
169
+ }
170
+ return crossed;
171
+ }
172
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"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"}