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
package/dist/plan.js ADDED
@@ -0,0 +1,482 @@
1
+ /**
2
+ * plan.ts: power economics. What will this cost, and can we even detect it?
3
+ *
4
+ * Without a plan, the first sign that a suite cannot be decided is a large bill
5
+ * and no certified case. This module tells you *before* you spend anything, and it will tell you that the honest answer is
6
+ * sometimes "you cannot detect this at any price".
7
+ *
8
+ * The three numbers that matter, per case:
9
+ *
10
+ * 1. **The bar.** e-BH at FDR q over m cases certifies a case on its own only
11
+ * once its e-value clears m/q. That is the target the evidence has to hit.
12
+ *
13
+ * 2. **The ceiling.** `evidenceCeilingLogE`, the limit of the unpaired
14
+ * two-sample e-value as candidate runs go to infinity. Both marginals are
15
+ * the same binomial likelihood under different priors, so everything that
16
+ * grows with n cancels and the Bayes factor converges to the prior density
17
+ * ratio at the observed rate. If the ceiling is under the bar, no number of
18
+ * candidate runs will certify the case. This is not a budget problem and no
19
+ * budget fixes it: **more baseline runs** fix it, by sharpening the null.
20
+ *
21
+ * 3. **The paired alternative.** Re-run the baseline alongside the candidate
22
+ * on the same seed and the null becomes an exact point mass at ½ (a
23
+ * discordant pair points either way with probability exactly one half under
24
+ * "nothing changed"). Nothing is estimated, so nothing caps the evidence:
25
+ * the paired e-value grows linearly in discordant pairs. It costs two runs
26
+ * per observation and it is still, routinely, the cheaper design, and it
27
+ * is the only one that works at all for a case whose ceiling is under the
28
+ * bar.
29
+ *
30
+ * Everything here is a *plan*: it evaluates the mean trajectory (the candidate
31
+ * scores exactly its expected successes). Realised sample counts vary around
32
+ * it.
33
+ */
34
+ import { baselineNullRate } from "./baseline.js";
35
+ import { PeeksafeError, requireErrorRates, requireOpenProbability, requireProbability, } from "./errors.js";
36
+ import { betaQuantile, ebhSoloThreshold, evidenceCeilingLogE, mcnemarSamplesForEvidence, pairPhi, sampleSizeTwoProportion, samplesForEvidence, sprtExpectedN, } from "./stats.js";
37
+ export const DEFAULT_PLAN = {
38
+ mde: 0.15,
39
+ fdr: 0.05,
40
+ // These three are *assumptions* until you measure them on a real run: cost
41
+ // and latency from your runner, coupling with `pairPhi` on a pilot.
42
+ costPerRunUsd: 0.0021,
43
+ msPerRun: 780,
44
+ pairCoupling: 0.9,
45
+ screenRuns: 4,
46
+ concurrency: 8,
47
+ runsPerCaseCeiling: 2000,
48
+ alpha: 0.05,
49
+ beta: 0.1,
50
+ minTrials: 8,
51
+ maxTrials: 96,
52
+ batchSize: 4,
53
+ nullQuantile: 0.5,
54
+ };
55
+ const round = (x) => (Number.isFinite(x) ? Math.ceil(x) : Infinity);
56
+ const medianTrials = (ps) => {
57
+ const xs = ps.map((p) => p.baseline.trials).sort((a, b) => a - b);
58
+ return xs.length === 0 ? 0 : xs[Math.floor(xs.length / 2)];
59
+ };
60
+ /**
61
+ * How many baseline runs would lift this case's evidence ceiling over the bar?
62
+ * The *smallest* such number, holding the baseline *rate* fixed, "if I keep
63
+ * measuring main at the same quality, how long until this case becomes
64
+ * decidable at all?"
65
+ *
66
+ * Smallest, and not a level the case stays decidable above: the ceiling is a
67
+ * sawtooth in the baseline size (see the comment in the body), so a baseline a
68
+ * few runs longer than this can fall back under the bar. Treat it as the floor
69
+ * it is rather than a promise about a bigger baseline.
70
+ */
71
+ function baselineRunsToClear(rate, mde, barLogE, cap = 100_000) {
72
+ const at = (n) => {
73
+ const s = Math.round(rate * n);
74
+ return evidenceCeilingLogE(Math.max(1e-6, Math.min(1 - 1e-6, rate - mde)), s, n, mde);
75
+ };
76
+ // `at` is not monotone in `n`, so this cannot bisect the way
77
+ // `samplesForEvidence` does. Holding the rate fixed still forces an integer
78
+ // success count, so the implied rate jitters around `rate` and the ceiling
79
+ // drops hard every time the count ticks over: at 99% losing 10 points it
80
+ // clears the bar at 49 runs, falls back under it at 51, and does not clear
81
+ // again until 71. Bisecting that returns whichever crossing the doubling
82
+ // happened to bracket, which is no particular quantity at all; it answered 81
83
+ // for the README's own case where 75 is enough, and 5 for every case that
84
+ // needed fewer, because it bisected (4, 8]. Doubling now only bounds the
85
+ // scan, and never steps past `cap`, so the answer is one this search checked.
86
+ let hi = 1;
87
+ while (hi < cap && at(hi) < barLogE)
88
+ hi = Math.min(cap, hi * 2);
89
+ if (at(hi) < barLogE)
90
+ return null;
91
+ for (let n = 1; n < hi; n++)
92
+ if (at(n) >= barLogE)
93
+ return n;
94
+ return hi;
95
+ }
96
+ export function planCase(c, b, cfg, m) {
97
+ const barLogE = Math.log(ebhSoloThreshold(m, cfg.fdr));
98
+ const trials = b?.trials ?? 0;
99
+ const successes = b?.successes ?? 0;
100
+ const rate = trials > 0 ? successes / trials : 0;
101
+ const alt = Math.max(0, rate - cfg.mde);
102
+ const base = {
103
+ caseId: c.id,
104
+ // `suite` groups cases into correlation families. A caller who does not
105
+ // group them gets one family per case, which is the conservative reading.
106
+ suite: c.suite ?? c.id,
107
+ baseline: { successes, trials, rate },
108
+ alternativeRate: alt,
109
+ barLogE,
110
+ fixedNPerArm: sampleSizeTwoProportion(Math.max(1e-6, rate), cfg.mde, cfg.alpha, cfg.beta),
111
+ };
112
+ if (trials === 0) {
113
+ return {
114
+ ...base,
115
+ ceilingLogE: 0,
116
+ unpaired: { runs: Infinity, costUsd: Infinity, wallMs: Infinity },
117
+ paired: { pairs: Infinity, runs: Infinity, costUsd: Infinity, wallMs: Infinity },
118
+ pairCoupling: cfg.pairCoupling,
119
+ detectability: "IMPOSSIBLE",
120
+ design: null,
121
+ baselineRunsNeeded: null,
122
+ reason: "no baseline: nothing to compare against, record one first",
123
+ };
124
+ }
125
+ if (rate <= cfg.mde) {
126
+ return {
127
+ ...base,
128
+ ceilingLogE: 0,
129
+ unpaired: { runs: Infinity, costUsd: Infinity, wallMs: Infinity },
130
+ paired: { pairs: Infinity, runs: Infinity, costUsd: Infinity, wallMs: Infinity },
131
+ pairCoupling: cfg.pairCoupling,
132
+ detectability: "IMPOSSIBLE",
133
+ design: null,
134
+ baselineRunsNeeded: null,
135
+ reason: `baseline rate ${(rate * 100).toFixed(0)}% cannot lose ${(cfg.mde * 100).toFixed(0)} points, ` +
136
+ `the effect is arithmetically impossible, not merely expensive`,
137
+ };
138
+ }
139
+ const pTrue = Math.max(1e-6, Math.min(1 - 1e-6, alt));
140
+ const ceilingLogE = evidenceCeilingLogE(pTrue, successes, trials, cfg.mde);
141
+ const unpairedRuns = samplesForEvidence(pTrue, successes, trials, cfg.mde, barLogE, cfg.runsPerCaseCeiling * 8);
142
+ const pairs = mcnemarSamplesForEvidence(rate, cfg.mde, cfg.pairCoupling, barLogE, cfg.runsPerCaseCeiling * 8);
143
+ const unpaired = {
144
+ runs: unpairedRuns,
145
+ costUsd: unpairedRuns * cfg.costPerRunUsd,
146
+ wallMs: unpairedRuns * cfg.msPerRun,
147
+ };
148
+ const paired = {
149
+ pairs,
150
+ runs: pairs * 2,
151
+ costUsd: pairs * 2 * cfg.costPerRunUsd,
152
+ wallMs: pairs * 2 * cfg.msPerRun,
153
+ };
154
+ let detectability;
155
+ let reason;
156
+ if (Number.isFinite(unpairedRuns) && unpairedRuns <= cfg.runsPerCaseCeiling) {
157
+ detectability = "UNPAIRED";
158
+ reason = `${unpairedRuns} candidate runs against the stored baseline`;
159
+ }
160
+ else if (Number.isFinite(pairs) && pairs * 2 <= cfg.runsPerCaseCeiling) {
161
+ detectability = "PAIRED_ONLY";
162
+ reason = Number.isFinite(unpairedRuns)
163
+ ? `unpaired needs ${unpairedRuns} runs (past the ${cfg.runsPerCaseCeiling} ceiling); paired needs ${pairs * 2}`
164
+ : `unpaired evidence ceiling is e^${ceilingLogE.toFixed(1)}, under the bar e^${barLogE.toFixed(1)}, ` +
165
+ `no number of candidate runs certifies it. Paired: ${pairs * 2} runs.`;
166
+ }
167
+ else if (Number.isFinite(unpairedRuns) || Number.isFinite(pairs)) {
168
+ detectability = "TOO_EXPENSIVE";
169
+ reason = `cheapest design needs ${Math.min(unpairedRuns, pairs * 2)} runs, past the ${cfg.runsPerCaseCeiling} ceiling`;
170
+ }
171
+ else {
172
+ detectability = "IMPOSSIBLE";
173
+ reason = `neither design reaches the bar within ${cfg.runsPerCaseCeiling * 8} runs`;
174
+ }
175
+ const baselineRunsNeeded = detectability === "UNPAIRED" ? null : baselineRunsToClear(rate, cfg.mde, barLogE);
176
+ const design = detectability === "PAIRED_ONLY"
177
+ ? "paired"
178
+ : detectability !== "UNPAIRED"
179
+ ? null
180
+ : paired.runs < unpaired.runs
181
+ ? "paired"
182
+ : "unpaired";
183
+ return {
184
+ ...base,
185
+ ceilingLogE,
186
+ unpaired,
187
+ paired,
188
+ pairCoupling: cfg.pairCoupling,
189
+ detectability,
190
+ design,
191
+ baselineRunsNeeded,
192
+ reason,
193
+ };
194
+ }
195
+ /**
196
+ * Expected total samples for the sequential design, priced under Wald's
197
+ * expected sample number at a supplied "true" rate per case, and clamped into
198
+ * the gate's own [minTrials, maxTrials] batch grid.
199
+ *
200
+ * This is the number that predicts what the gate will actually spend.
201
+ * `samplesForEvidence`, the one behind `PlanCase.unpaired.runs`, answers a
202
+ * different question: how many runs it takes to *certify a regression*. A case
203
+ * that did not regress never gets there; it hits the H₀ wall in a few batches
204
+ * and stops. Pricing every case at the certification cost over-predicts the
205
+ * bill.
206
+ *
207
+ * `trueRate` defaults to the case's own baseline rate, i.e. "nothing moved", * which is what almost every pull request looks like.
208
+ */
209
+ export function expectedSequentialSamples(cases, baseline, cfg, trueRate = (_id, b) => b.trials > 0 ? b.successes / b.trials : 0) {
210
+ const perCase = new Map();
211
+ let total = 0;
212
+ for (const c of cases) {
213
+ const b = baseline.get(c.id);
214
+ if (!b || b.trials === 0)
215
+ continue;
216
+ const p0 = baselineNullRate(b, cfg.nullQuantile);
217
+ const p1 = Math.max(0.005, p0 - cfg.mde);
218
+ const p = Math.max(0, Math.min(1, trueRate(c.id, b)));
219
+ const raw = sprtExpectedN(p, p0, p1, cfg.alpha, cfg.beta);
220
+ const n = Number.isFinite(raw) && raw > 0
221
+ ? Math.min(cfg.maxTrials, Math.max(cfg.minTrials, Math.ceil(raw)))
222
+ : cfg.maxTrials;
223
+ // The gate only ever adds whole batches past the minimum, so round the
224
+ // prediction onto the same grid the gate samples on.
225
+ const batched = cfg.minTrials + Math.ceil(Math.max(0, n - cfg.minTrials) / cfg.batchSize) * cfg.batchSize;
226
+ const capped = Math.min(cfg.maxTrials, batched);
227
+ perCase.set(c.id, capped);
228
+ total += capped;
229
+ }
230
+ return { samples: total, perCase };
231
+ }
232
+ /**
233
+ * Plan a suite. `pilots` holds a pair table per case from a pilot run of the
234
+ * paired design, ideally with both arms on the same revision; a case with one
235
+ * prices pairing at its measured `pairPhi` instead of `cfg.pairCoupling`, so
236
+ * its `design` is chosen from a coupling the runner actually delivers.
237
+ */
238
+ export function makePlan(cases, baseline, cfg = DEFAULT_PLAN, pilots = new Map()) {
239
+ // Open interval: mde = 0 asks for a zero-point drop and fdr = 0 asks for a
240
+ // bar of Infinity. Both used to be accepted and produce a plan in which
241
+ // nothing is decidable, for a reason the printed message blamed on the
242
+ // baseline.
243
+ requireOpenProbability(cfg.mde, "mde", "makePlan");
244
+ requireOpenProbability(cfg.fdr, "fdr", "makePlan");
245
+ // The two error rates the SPRT costing runs on. Left unchecked they reached
246
+ // `sprtExpectedN`, which answers NaN or Infinity outside (0,1), and both
247
+ // callers of it here gate on `Number.isFinite(raw) && raw > 0` and quote
248
+ // `maxTrials` when that fails. So an alpha of 2 produced a complete plan with
249
+ // every case at the per-case cap and no error anywhere.
250
+ requireErrorRates(cfg.alpha, cfg.beta, "makePlan");
251
+ requireProbability(cfg.pairCoupling, "pairCoupling", "makePlan");
252
+ // Refused by `baselineNullRate` already, but only once a case with a baseline
253
+ // reaches it, and under that function's name for it, `q`. Checked here so
254
+ // every planner entry point refuses it the same way and names it.
255
+ requireOpenProbability(cfg.nullQuantile, "nullQuantile", "makePlan");
256
+ if (cases.length === 0) {
257
+ throw new PeeksafeError("PEEKSAFE_E_SUITE_EMPTY", "makePlan: no cases to plan for", {
258
+ hint: "pass at least one case; makePlan prices a suite, not an empty list",
259
+ });
260
+ }
261
+ if (!(cfg.costPerRunUsd >= 0) || !(cfg.msPerRun >= 0)) {
262
+ throw new PeeksafeError("PEEKSAFE_E_CONFIG", "makePlan: costPerRunUsd and msPerRun must be ≥ 0", {
263
+ detail: { costPerRunUsd: cfg.costPerRunUsd, msPerRun: cfg.msPerRun },
264
+ });
265
+ }
266
+ const m = cases.length;
267
+ const planned = cases.map((c) => {
268
+ const pilot = pilots.get(c.id);
269
+ // pairPhi validates the counts, so a malformed pilot is refused even when it sums to 0.
270
+ const phi = pilot ? pairPhi(pilot) : 0;
271
+ // An empty pilot measured nothing, so it leaves the configured coupling in place.
272
+ const measured = pilot && pilot.bothPass + pilot.worse + pilot.better + pilot.bothFail > 0;
273
+ // A negative phi is noise around independence for a design that couples by construction.
274
+ const caseCfg = measured ? { ...cfg, pairCoupling: Math.max(0, phi) } : cfg;
275
+ return planCase(c, baseline.get(c.id), caseCfg, m);
276
+ });
277
+ const finite = (x) => (Number.isFinite(x) ? x : 0);
278
+ const decidableUnpaired = planned.filter((p) => p.detectability === "UNPAIRED");
279
+ const decidableAny = planned.filter((p) => p.detectability === "UNPAIRED" || p.detectability === "PAIRED_ONLY");
280
+ const unpairedRuns = decidableUnpaired.reduce((a, p) => a + finite(p.unpaired.runs), 0);
281
+ const pairedRuns = decidableAny.reduce((a, p) => a + finite(p.paired.runs), 0);
282
+ // Per case, take the cheaper design that can actually decide it. This is the
283
+ // number the recommendation and the affordability verdict are built on: a
284
+ // design that is cheap because it decides nothing is not cheap.
285
+ const bestRuns = decidableAny.reduce((a, p) => a + Math.min(finite(p.unpaired.runs) || Infinity, finite(p.paired.runs) || Infinity), 0);
286
+ const pairedOnlyCount = planned.filter((p) => p.detectability === "PAIRED_ONLY").length;
287
+ // The design the recommendation will name, computed here because the
288
+ // *expected* bill has to be priced for it. `pairedOnly > 25%` is the same
289
+ // condition the recommendation string uses; they cannot drift apart.
290
+ const recommendedDesign = cfg.design ??
291
+ (decidableAny.length > 0 && pairedOnlyCount > cases.length * 0.25 ? "paired" : "unpaired");
292
+ const runsPerObservation = recommendedDesign === "paired" ? 2 : 1;
293
+ // Screening: everything gets `screenRuns`; only the cases the screen ranks as
294
+ // plausibly moved go on to the sequential test. A clean PR moves nothing, so
295
+ // in expectation only the FDR-sized tail continues, but a plan must be
296
+ // conservative, so we budget for the worst realistic case: 10% continue.
297
+ const screenContinueFraction = 0.1;
298
+ const screenSelected = Math.ceil(decidableAny.length * screenContinueFraction);
299
+ const meanBest = decidableAny.length > 0 ? bestRuns / decidableAny.length : 0;
300
+ // `screenRuns` counts *observations*, and a paired observation is two runs,
301
+ // so a paired screening pass costs twice what an unpaired one does. Only the
302
+ // screen term is scaled here: `meanBest` already comes from `paired.runs`,
303
+ // which is `pairs * 2`. The expected total below reads `screenRuns` the same
304
+ // way, and when this one did not, the two screened figures in `totals`
305
+ // disagreed about the price of the same pass.
306
+ const screenedRuns = m * cfg.screenRuns * runsPerObservation + screenSelected * meanBest;
307
+ const fixedNRuns = planned.reduce((a, p) => a + finite(p.fixedNPerArm), 0);
308
+ // The expected cost of a pull request that changed nothing, which is what a
309
+ // pull request almost always is, and therefore the number a budget should be
310
+ // set from.
311
+ // `expectedSequentialSamples` counts *observations*. A paired observation is
312
+ // two runs, the candidate and the reference revision on the same seed, and
313
+ // so is a paired screening run.
314
+ const typical = expectedSequentialSamples(cases, baseline, cfg);
315
+ const typicalRuns = typical.samples * runsPerObservation;
316
+ const meanTypical = typical.perCase.size > 0 ? typical.samples / typical.perCase.size : 0;
317
+ const typicalScreenedRuns = Math.ceil((m * cfg.screenRuns + Math.ceil(m * screenContinueFraction) * meanTypical) * runsPerObservation);
318
+ const totals = {
319
+ cases: m,
320
+ decidableUnpaired: decidableUnpaired.length,
321
+ decidableBest: decidableAny.length,
322
+ unpairedRuns,
323
+ unpairedCostUsd: unpairedRuns * cfg.costPerRunUsd,
324
+ pairedRuns,
325
+ pairedCostUsd: pairedRuns * cfg.costPerRunUsd,
326
+ bestRuns: round(bestRuns),
327
+ bestCostUsd: round(bestRuns) * cfg.costPerRunUsd,
328
+ screenedRuns: round(screenedRuns),
329
+ screenedCostUsd: round(screenedRuns) * cfg.costPerRunUsd,
330
+ fixedNRuns,
331
+ fixedNCostUsd: fixedNRuns * cfg.costPerRunUsd,
332
+ wallMsUnpaired: (unpairedRuns * cfg.msPerRun) / Math.max(1, cfg.concurrency),
333
+ wallMsPaired: (pairedRuns * cfg.msPerRun) / Math.max(1, cfg.concurrency),
334
+ wallMsBest: (round(bestRuns) * cfg.msPerRun) / Math.max(1, cfg.concurrency),
335
+ wallMsScreened: (round(screenedRuns) * cfg.msPerRun) / Math.max(1, cfg.concurrency),
336
+ screenContinueFraction,
337
+ runsPerObservation,
338
+ typicalRuns,
339
+ typicalCostUsd: typicalRuns * cfg.costPerRunUsd,
340
+ wallMsTypical: (typicalRuns * cfg.msPerRun) / Math.max(1, cfg.concurrency),
341
+ typicalScreenedRuns,
342
+ typicalScreenedCostUsd: typicalScreenedRuns * cfg.costPerRunUsd,
343
+ };
344
+ const undecidable = planned.filter((p) => p.detectability === "TOO_EXPENSIVE" || p.detectability === "IMPOSSIBLE");
345
+ const pairedOnly = planned.filter((p) => p.detectability === "PAIRED_ONLY");
346
+ const impossible = planned.filter((p) => p.detectability === "IMPOSSIBLE");
347
+ const recommendation = decidableAny.length === 0
348
+ ? `nothing is decidable at ${(cfg.mde * 100).toFixed(0)} points against this baseline, record more baseline runs or raise the MDE`
349
+ : recommendedDesign === "paired"
350
+ ? `pair the runs: ${pairedOnly.length}/${m} cases have an unpaired evidence ceiling under the bar, so no candidate budget can decide them` +
351
+ ` (the costs below are priced at two runs an observation, which is what pairing costs)`
352
+ : totals.screenedCostUsd < totals.bestCostUsd
353
+ ? `screen first, then run the sequential test on what the screen keeps ` +
354
+ `($${totals.screenedCostUsd.toFixed(2)} vs $${totals.bestCostUsd.toFixed(2)})`
355
+ : `run the sequential test directly`;
356
+ // The honest cost is the cheapest plan that still decides something. A design
357
+ // that is free because it certifies nothing is not a cheap design.
358
+ const ceilingCost = decidableAny.length === 0 ? Infinity : Math.min(totals.screenedCostUsd, totals.bestCostUsd);
359
+ // The headline is the expected cost; leading with the ceiling over-states the bill.
360
+ // Mirrors the ceiling's structure, the cheapest plan of its kind, so the two
361
+ // numbers are roughly comparable. Comparing an unscreened expected cost
362
+ // with a screened ceiling was the first version of this and it made the
363
+ // "expected" figure look *larger* than the worst case.
364
+ const expectedCost = decidableAny.length === 0
365
+ ? Infinity
366
+ : Math.min(totals.typicalCostUsd, totals.typicalScreenedCostUsd);
367
+ const coverage = `${decidableAny.length}/${m} cases`;
368
+ // "Every case regressed" is not the expensive pull request, and calling it
369
+ // the worst case put a number below the headline that an operator would
370
+ // budget from: a 200-case suite on a 12-run baseline read "about $11.42 per
371
+ // pull request. Worst case ... is $9.16". The sequential test stops a case
372
+ // the moment it clears the bar, so a suite that really did regress finishes
373
+ // early, while one that did not move runs its full schedule. When the
374
+ // all-regressed cost lands under the expected one, say which it is.
375
+ //
376
+ // The sentence stops short of naming early stopping as *the* cause, because
377
+ // there is a second one: both figures are a `min` over two designs, and the
378
+ // two minima need not select the same design, so the cheaper figure may be
379
+ // pricing a plan the dearer one is not. Until both sides are made to select
380
+ // together, the honest claim is only that neither bounds the other.
381
+ const ceilingNote = !Number.isFinite(ceilingCost)
382
+ ? ""
383
+ : ceilingCost >= expectedCost
384
+ ? ` Worst case, every case regressing at once, is $${ceilingCost.toFixed(2)}.`
385
+ : ` A pull request in which every case regressed costs less, $${ceilingCost.toFixed(2)}: ` +
386
+ `the two do not price the same schedule, so neither bounds the other.`;
387
+ const verdict = !Number.isFinite(expectedCost)
388
+ ? `undecidable: no design reaches the bar for any case at ${(cfg.mde * 100).toFixed(0)} points with a ${medianTrials(planned)}-run baseline`
389
+ : expectedCost <= 5
390
+ ? `affordable: about $${expectedCost.toFixed(2)} per pull request, covering ${coverage}.${ceilingNote}`
391
+ : expectedCost <= 100
392
+ ? `viable but not free: about $${expectedCost.toFixed(2)} per pull request covering ${coverage}, worth gating a release on, not every commit.${ceilingNote}`
393
+ : `not affordable per-PR at $${cfg.costPerRunUsd.toFixed(4)}/run: about $${expectedCost.toFixed(0)} a time for ${coverage}. ` +
394
+ `Cut the suite, raise the MDE, or gate nightly instead of per-PR.${ceilingNote}`;
395
+ return {
396
+ config: cfg,
397
+ cases: planned,
398
+ totals,
399
+ undecidable,
400
+ pairedOnly,
401
+ impossible,
402
+ recommendedDesign,
403
+ recommendation,
404
+ verdict,
405
+ expectedCostUsd: expectedCost,
406
+ ceilingCostUsd: ceilingCost,
407
+ };
408
+ }
409
+ export function affordabilityGrid(baselineRate, baselineTrials, cfg, budgetUsd, caseCounts = [20, 50, 100, 200], mdes = [0.1, 0.15, 0.25, 0.35]) {
410
+ requireProbability(baselineRate, "baselineRate", "affordabilityGrid");
411
+ requireErrorRates(cfg.alpha, cfg.beta, "affordabilityGrid");
412
+ // The typical bill below takes this quantile of the baseline posterior
413
+ // straight from `betaQuantile`, which answers 0 or 1 for a quantile outside
414
+ // (0,1) and about 0 for NaN instead of refusing. `makePlan` refuses it.
415
+ requireOpenProbability(cfg.nullQuantile, "nullQuantile", "affordabilityGrid");
416
+ // Every other planner entry point refuses a case with no baseline: `planCase`
417
+ // returns IMPOSSIBLE with "no baseline", `evaluatePoint` throws on
418
+ // `baselineRuns < 1`, and `gate` throws PEEKSAFE_E_BASELINE_MISSING. This one
419
+ // checked only the rate, so `baselineTrials: 0` reached `twoSamplePriors` as
420
+ // 0 successes in 0 trials, whose null prior is Beta(1, 1), and the grid
421
+ // priced the suite against "every case passes half the time" and reported it
422
+ // affordable, on a cheaper typical bill than the same call at a real
423
+ // baseline. A fractional or negative count did reach a refusal, but from
424
+ // `requireCounts` three frames down, naming a success count the caller never
425
+ // passed. `test/budget.test.ts` has the figures.
426
+ if (!Number.isInteger(baselineTrials) || baselineTrials < 1) {
427
+ throw new PeeksafeError("PEEKSAFE_E_CONFIG", `affordabilityGrid: baselineTrials must be a positive integer, got ${baselineTrials}`, {
428
+ detail: { baselineTrials },
429
+ hint: 'this grid prices a suite against a recorded baseline; with none the null prior is Beta(1, 1), which reads as "every case passes half the time"',
430
+ });
431
+ }
432
+ const out = [];
433
+ for (const m of caseCounts) {
434
+ for (const mde of mdes) {
435
+ const bar = Math.log(ebhSoloThreshold(m, cfg.fdr));
436
+ const pTrue = Math.max(1e-6, baselineRate - mde);
437
+ const s = Math.round(baselineRate * baselineTrials);
438
+ const perCase = samplesForEvidence(pTrue, s, baselineTrials, mde, bar, 200_000);
439
+ const pairs = mcnemarSamplesForEvidence(baselineRate, mde, cfg.pairCoupling, bar, 200_000);
440
+ // the cheaper of the two designs, screened down to a 10% continue rate
441
+ const best = Math.min(Number.isFinite(perCase) ? perCase : Infinity, pairs * 2);
442
+ // Which design `best` just picked, needed by both totals below: they have
443
+ // to price the same screening pass as each other, and `screenRuns` counts
444
+ // observations, so a paired pass costs two runs an observation. Only the
445
+ // screen term is scaled, because `best` is already in runs (`pairs * 2`).
446
+ // When this total left the screen term unscaled it understated a paired
447
+ // cell by `m * screenRuns` runs, which made `affordable` below answer for
448
+ // a cheaper ceiling than the one it reports, and made this grid disagree
449
+ // with `makePlan`'s `screenedRuns` about the price of the same pass.
450
+ const runsPerObs = Number.isFinite(perCase) && perCase <= pairs * 2 ? 1 : 2;
451
+ const runs = Number.isFinite(best)
452
+ ? Math.ceil(m * cfg.screenRuns * runsPerObs + Math.ceil(m * 0.1) * best)
453
+ : Infinity;
454
+ const costUsd = runs * cfg.costPerRunUsd;
455
+ // The ceiling above prices a pull request in which every case regressed.
456
+ // This prices one in which nothing did, which is what a pull request
457
+ // almost always is.
458
+ const p0 = betaQuantile(1 + s, 1 + baselineTrials - s, cfg.nullQuantile);
459
+ const p1 = Math.max(0.005, p0 - mde);
460
+ const rawN = sprtExpectedN(baselineRate, p0, p1, cfg.alpha, cfg.beta);
461
+ const perCaseTypical = Number.isFinite(rawN) && rawN > 0
462
+ ? Math.min(cfg.maxTrials, Math.max(cfg.minTrials, Math.ceil(rawN)))
463
+ : cfg.maxTrials;
464
+ // Priced for the same design as the ceiling above (`runsPerObs`), or the
465
+ // cell quotes an unpaired price for a plan only pairing can execute.
466
+ const typicalRuns = Math.ceil((m * cfg.screenRuns + Math.ceil(m * 0.1) * perCaseTypical) * runsPerObs);
467
+ const typicalCostUsd = typicalRuns * cfg.costPerRunUsd;
468
+ out.push({
469
+ cases: m,
470
+ mde,
471
+ runs,
472
+ costUsd,
473
+ typicalRuns,
474
+ typicalCostUsd,
475
+ affordable: costUsd <= budgetUsd,
476
+ typicallyAffordable: typicalCostUsd <= budgetUsd,
477
+ });
478
+ }
479
+ }
480
+ return out;
481
+ }
482
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"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"}
package/dist/rand.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ export interface Rand {
2
+ (): number;
3
+ /** A single Bernoulli draw at rate `p`. */
4
+ bernoulli(p: number): boolean;
5
+ /** A uniform integer in `[0, nExclusive)`. */
6
+ int(nExclusive: number): number;
7
+ /** A uniform choice. Throws on an empty array rather than returning undefined. */
8
+ pick<T>(xs: readonly T[]): T;
9
+ /** A standard normal draw, via Box-Muller. */
10
+ normal(): number;
11
+ }
12
+ /** FNV-1a. Used to derive an independent seed per named stream. */
13
+ export declare function hash32(s: string): number;
14
+ export declare function makeRand(seed: number | string): Rand;
15
+ /** A stream keyed by name, reproducible regardless of call order elsewhere. */
16
+ export declare const streamFor: (...parts: Array<string | number>) => Rand;
17
+ //# sourceMappingURL=rand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rand.d.ts","sourceRoot":"","sources":["../src/rand.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,IAAI;IACnB,IAAI,MAAM,CAAC;IACX,2CAA2C;IAC3C,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,8CAA8C;IAC9C,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,kFAAkF;IAClF,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,CAAC;IAC7B,8CAA8C;IAC9C,MAAM,IAAI,MAAM,CAAC;CAClB;AAgCD,mEAAmE;AACnE,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAOxC;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAwCpD;AAED,+EAA+E;AAC/E,eAAO,MAAM,SAAS,aAAc,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,KAAG,IAC1B,CAAC"}
package/dist/rand.js ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * rand.ts: deterministic randomness.
3
+ *
4
+ * Everything stochastic in peeksafe (the Monte Carlo checks in `test/`, and any
5
+ * simulation a caller wants to run) draws from here.
6
+ *
7
+ * The generator is a linear congruential generator feeding an integer avalanche.
8
+ * The LCG alone is not good enough: consecutive outputs of an LCG sit on a
9
+ * lattice, which visibly biases Bernoulli streams, and a Bernoulli stream is the
10
+ * only thing this library ever simulates. The mixer decorrelates without adding
11
+ * a dependency and without losing determinism.
12
+ *
13
+ * Both sets of constants are published parameters rather than anybody's source
14
+ * code, and both are named in NOTICE.
15
+ *
16
+ * The exact stream matters. The measurements in the README and in
17
+ * `test/paper.test.ts` were produced by this generator, so replacing it with a
18
+ * better one (sfc32, pcg) would change every reported figure. That is a fine
19
+ * thing to do deliberately, with the numbers re-derived and the README updated.
20
+ * It is not a fine thing to do by accident, which is why the constants are
21
+ * spelled out here rather than imported.
22
+ */
23
+ import { PeeksafeError } from "./errors.js";
24
+ /**
25
+ * LCG with Knuth's multiplier 1664525 and increment 1013904223, modulo 2^32.
26
+ *
27
+ * These are tabulated constants (Knuth, TAOCP vol. 2), reprinted in most
28
+ * numerical texts. Exact in float64: 2^32 * 1664525 sits under 2^53, so the
29
+ * product never loses a bit before the mask.
30
+ */
31
+ function lcg(seed) {
32
+ let s = seed >>> 0;
33
+ return () => {
34
+ s = (s * 1664525 + 1013904223) >>> 0;
35
+ return s / 0x100000000;
36
+ };
37
+ }
38
+ /**
39
+ * 32-bit avalanche: the `lowbias32` multipliers from Chris Wellons' hash
40
+ * function prospector, which he placed in the public domain. Two rounds of
41
+ * xor-shift-multiply, chosen by search for low bias rather than derived.
42
+ */
43
+ function mix32(x) {
44
+ let h = x >>> 0;
45
+ h ^= h >>> 16;
46
+ h = Math.imul(h, 0x7feb352d) >>> 0;
47
+ h ^= h >>> 15;
48
+ h = Math.imul(h, 0x846ca68b) >>> 0;
49
+ h ^= h >>> 16;
50
+ return h >>> 0;
51
+ }
52
+ /** FNV-1a. Used to derive an independent seed per named stream. */
53
+ export function hash32(s) {
54
+ let h = 0x811c9dc5;
55
+ for (let i = 0; i < s.length; i++) {
56
+ h ^= s.charCodeAt(i);
57
+ h = Math.imul(h, 0x01000193) >>> 0;
58
+ }
59
+ return h >>> 0;
60
+ }
61
+ export function makeRand(seed) {
62
+ const s = typeof seed === "string" ? hash32(seed) : seed >>> 0;
63
+ const base = lcg(s === 0 ? 1 : s);
64
+ const next = (() => mix32(Math.floor(base() * 0x100000000)) / 0x100000000);
65
+ next.bernoulli = (p) => next() < p;
66
+ next.int = (n) => {
67
+ if (!Number.isInteger(n) || n < 1) {
68
+ throw new PeeksafeError("PEEKSAFE_E_STAT_DOMAIN", `rand.int: n must be a positive integer, got ${n}`, {
69
+ detail: { n },
70
+ });
71
+ }
72
+ return Math.floor(next() * n) % n;
73
+ };
74
+ // Typed `T`, so it must never hand back `undefined`. Returning `xs[0]` for an
75
+ // empty array is `undefined` wearing a `T`, which is how a fail-open bug gets
76
+ // past the type checker.
77
+ next.pick = (xs) => {
78
+ if (xs.length === 0) {
79
+ throw new PeeksafeError("PEEKSAFE_E_STAT_DOMAIN", "rand.pick: cannot pick from an empty array");
80
+ }
81
+ return xs[next.int(xs.length)];
82
+ };
83
+ next.normal = () => {
84
+ const u = Math.max(1e-12, next());
85
+ const v = next();
86
+ return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
87
+ };
88
+ return next;
89
+ }
90
+ /** A stream keyed by name, reproducible regardless of call order elsewhere. */
91
+ export const streamFor = (...parts) => makeRand(hash32(parts.join("|")));
92
+ //# sourceMappingURL=rand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rand.js","sourceRoot":"","sources":["../src/rand.ts"],"names":[],"mappings":"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"}