@delmaredigital/payload-experiments 0.0.0-stage → 0.1.0-rc.3

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 (466) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +582 -2
  3. package/dist/access.d.ts +1 -0
  4. package/dist/access.js +2 -0
  5. package/dist/admin/AllocationField.d.ts +2 -0
  6. package/dist/admin/AllocationField.js +80 -0
  7. package/dist/admin/FlagPointSelect.d.ts +2 -0
  8. package/dist/admin/FlagPointSelect.js +54 -0
  9. package/dist/admin/LayerUsage.d.ts +2 -0
  10. package/dist/admin/LayerUsage.js +24 -0
  11. package/dist/admin/LockedFieldNotice.d.ts +2 -0
  12. package/dist/admin/LockedFieldNotice.js +26 -0
  13. package/dist/admin/MetricSourceSelect.d.ts +2 -0
  14. package/dist/admin/MetricSourceSelect.js +32 -0
  15. package/dist/admin/PhaseHistory.d.ts +2 -0
  16. package/dist/admin/PhaseHistory.js +76 -0
  17. package/dist/admin/PreviewLinks.d.ts +11 -0
  18. package/dist/admin/PreviewLinks.js +69 -0
  19. package/dist/admin/StatusCell.d.ts +3 -0
  20. package/dist/admin/StatusCell.js +19 -0
  21. package/dist/admin/SummaryCell.d.ts +2 -0
  22. package/dist/admin/SummaryCell.js +32 -0
  23. package/dist/admin/actions/AddMetricDialog.d.ts +6 -0
  24. package/dist/admin/actions/AddMetricDialog.js +138 -0
  25. package/dist/admin/actions/AllocateDialog.d.ts +6 -0
  26. package/dist/admin/actions/AllocateDialog.js +81 -0
  27. package/dist/admin/actions/ConfirmDialog.d.ts +14 -0
  28. package/dist/admin/actions/ConfirmDialog.js +192 -0
  29. package/dist/admin/actions/DecideDialog.d.ts +7 -0
  30. package/dist/admin/actions/DecideDialog.js +173 -0
  31. package/dist/admin/actions/ExperimentActions.d.ts +3 -0
  32. package/dist/admin/actions/ExperimentActions.js +110 -0
  33. package/dist/admin/actions/MakeContentResult.d.ts +6 -0
  34. package/dist/admin/actions/MakeContentResult.js +50 -0
  35. package/dist/admin/actions/actions.model.d.ts +37 -0
  36. package/dist/admin/actions/actions.model.js +71 -0
  37. package/dist/admin/actions/decide.model.d.ts +21 -0
  38. package/dist/admin/actions/decide.model.js +38 -0
  39. package/dist/admin/allocation.model.d.ts +20 -0
  40. package/dist/admin/allocation.model.js +38 -0
  41. package/dist/admin/api.d.ts +32 -0
  42. package/dist/admin/api.js +95 -0
  43. package/dist/admin/armSelect/ArmSelectField.d.ts +2 -0
  44. package/dist/admin/armSelect/ArmSelectField.js +185 -0
  45. package/dist/admin/armSelect/armSelect.model.d.ts +30 -0
  46. package/dist/admin/armSelect/armSelect.model.js +20 -0
  47. package/dist/admin/arms/ArmsField.d.ts +2 -0
  48. package/dist/admin/arms/ArmsField.js +310 -0
  49. package/dist/admin/arms/ParamsEditor.d.ts +16 -0
  50. package/dist/admin/arms/ParamsEditor.js +79 -0
  51. package/dist/admin/arms/arms.model.d.ts +21 -0
  52. package/dist/admin/arms/arms.model.js +153 -0
  53. package/dist/admin/arms/pageTarget.model.d.ts +4 -0
  54. package/dist/admin/arms/pageTarget.model.js +19 -0
  55. package/dist/admin/arms/params.model.d.ts +7 -0
  56. package/dist/admin/arms/params.model.js +35 -0
  57. package/dist/admin/copy/actions.d.ts +38 -0
  58. package/dist/admin/copy/actions.js +38 -0
  59. package/dist/admin/copy/armSelect.d.ts +11 -0
  60. package/dist/admin/copy/armSelect.js +11 -0
  61. package/dist/admin/copy/arms.d.ts +37 -0
  62. package/dist/admin/copy/arms.js +47 -0
  63. package/dist/admin/copy/common.d.ts +20 -0
  64. package/dist/admin/copy/common.js +27 -0
  65. package/dist/admin/copy/history.d.ts +9 -0
  66. package/dist/admin/copy/history.js +9 -0
  67. package/dist/admin/copy/metrics.d.ts +9 -0
  68. package/dist/admin/copy/metrics.js +9 -0
  69. package/dist/admin/copy/results.d.ts +78 -0
  70. package/dist/admin/copy/results.js +81 -0
  71. package/dist/admin/copy/review.d.ts +33 -0
  72. package/dist/admin/copy/review.js +127 -0
  73. package/dist/admin/copy/status.d.ts +8 -0
  74. package/dist/admin/copy/status.js +8 -0
  75. package/dist/admin/copy/tabs.d.ts +7 -0
  76. package/dist/admin/copy/tabs.js +7 -0
  77. package/dist/admin/copy/who.d.ts +36 -0
  78. package/dist/admin/copy/who.js +36 -0
  79. package/dist/admin/labels.d.ts +45 -0
  80. package/dist/admin/labels.js +53 -0
  81. package/dist/admin/layerUsage.model.d.ts +15 -0
  82. package/dist/admin/layerUsage.model.js +18 -0
  83. package/dist/admin/lockedNotice.model.d.ts +3 -0
  84. package/dist/admin/lockedNotice.model.js +18 -0
  85. package/dist/admin/metricSource.model.d.ts +7 -0
  86. package/dist/admin/metricSource.model.js +11 -0
  87. package/dist/admin/paths.d.ts +16 -0
  88. package/dist/admin/paths.js +16 -0
  89. package/dist/admin/permissions.model.d.ts +10 -0
  90. package/dist/admin/permissions.model.js +4 -0
  91. package/dist/admin/phaseHistory.model.d.ts +19 -0
  92. package/dist/admin/phaseHistory.model.js +19 -0
  93. package/dist/admin/reload.d.ts +1 -0
  94. package/dist/admin/reload.js +1 -0
  95. package/dist/admin/results/ArmTable.d.ts +8 -0
  96. package/dist/admin/results/ArmTable.js +112 -0
  97. package/dist/admin/results/CappedCell.d.ts +4 -0
  98. package/dist/admin/results/CappedCell.js +24 -0
  99. package/dist/admin/results/CiChart.d.ts +10 -0
  100. package/dist/admin/results/CiChart.js +178 -0
  101. package/dist/admin/results/DataQualityPanel.d.ts +4 -0
  102. package/dist/admin/results/DataQualityPanel.js +40 -0
  103. package/dist/admin/results/ResultsView.d.ts +3 -0
  104. package/dist/admin/results/ResultsView.js +312 -0
  105. package/dist/admin/results/VerdictCard.d.ts +9 -0
  106. package/dist/admin/results/VerdictCard.js +49 -0
  107. package/dist/admin/results/chart.model.d.ts +32 -0
  108. package/dist/admin/results/chart.model.js +52 -0
  109. package/dist/admin/results/format.d.ts +10 -0
  110. package/dist/admin/results/format.js +55 -0
  111. package/dist/admin/results/results.model.d.ts +105 -0
  112. package/dist/admin/results/results.model.js +175 -0
  113. package/dist/admin/review/ReviewPanel.d.ts +2 -0
  114. package/dist/admin/review/ReviewPanel.js +268 -0
  115. package/dist/admin/review/review.model.d.ts +31 -0
  116. package/dist/admin/review/review.model.js +40 -0
  117. package/dist/admin/status.model.d.ts +16 -0
  118. package/dist/admin/status.model.js +17 -0
  119. package/dist/admin/styles.css +10 -0
  120. package/dist/admin/styles.d.ts +1 -0
  121. package/dist/admin/styles.js +2 -0
  122. package/dist/admin/summaryBatcher.d.ts +8 -0
  123. package/dist/admin/summaryBatcher.js +33 -0
  124. package/dist/admin/targeting/TargetingAttributeField.d.ts +2 -0
  125. package/dist/admin/targeting/TargetingAttributeField.js +31 -0
  126. package/dist/admin/targeting/TargetingOpField.d.ts +2 -0
  127. package/dist/admin/targeting/TargetingOpField.js +45 -0
  128. package/dist/admin/targeting/TargetingValueField.d.ts +2 -0
  129. package/dist/admin/targeting/TargetingValueField.js +142 -0
  130. package/dist/admin/targeting/targeting.model.d.ts +18 -0
  131. package/dist/admin/targeting/targeting.model.js +48 -0
  132. package/dist/admin/useApi.d.ts +20 -0
  133. package/dist/admin/useApi.js +10 -0
  134. package/dist/admin/useLayerUsage.d.ts +15 -0
  135. package/dist/admin/useLayerUsage.js +92 -0
  136. package/dist/client/ExperimentPageview.d.ts +1 -0
  137. package/dist/client/ExperimentPageview.js +27 -0
  138. package/dist/client/ExperimentsProvider.d.ts +6 -0
  139. package/dist/client/ExperimentsProvider.js +22 -0
  140. package/dist/client/ViewportExposure.d.ts +9 -0
  141. package/dist/client/ViewportExposure.js +69 -0
  142. package/dist/client/beacon.d.ts +17 -0
  143. package/dist/client/beacon.js +71 -0
  144. package/dist/client/context.d.ts +2 -0
  145. package/dist/client/context.js +2 -0
  146. package/dist/client/idempotency.d.ts +2 -0
  147. package/dist/client/idempotency.js +13 -0
  148. package/dist/client/trackConversionBeacon.d.ts +3 -0
  149. package/dist/client/trackConversionBeacon.js +16 -0
  150. package/dist/client/useExperiment.d.ts +5 -0
  151. package/dist/client/useExperiment.js +27 -0
  152. package/dist/client/useFlagPoint.d.ts +9 -0
  153. package/dist/client/useFlagPoint.js +32 -0
  154. package/dist/collections/access.d.ts +21 -0
  155. package/dist/collections/access.js +25 -0
  156. package/dist/collections/events.d.ts +3 -0
  157. package/dist/collections/events.js +117 -0
  158. package/dist/collections/experiments/armInvariants.d.ts +12 -0
  159. package/dist/collections/experiments/armInvariants.js +89 -0
  160. package/dist/collections/experiments/config.d.ts +3 -0
  161. package/dist/collections/experiments/config.js +574 -0
  162. package/dist/collections/experiments/createDefaults.d.ts +2 -0
  163. package/dist/collections/experiments/createDefaults.js +7 -0
  164. package/dist/collections/experiments/deleteGuard.d.ts +3 -0
  165. package/dist/collections/experiments/deleteGuard.js +8 -0
  166. package/dist/collections/experiments/editability.d.ts +4 -0
  167. package/dist/collections/experiments/editability.js +35 -0
  168. package/dist/collections/experiments/flagControlParams.d.ts +2 -0
  169. package/dist/collections/experiments/flagControlParams.js +32 -0
  170. package/dist/collections/experiments/lockHook.d.ts +2 -0
  171. package/dist/collections/experiments/lockHook.js +134 -0
  172. package/dist/collections/identities.d.ts +3 -0
  173. package/dist/collections/identities.js +30 -0
  174. package/dist/collections/index.d.ts +6 -0
  175. package/dist/collections/index.js +24 -0
  176. package/dist/collections/layers.d.ts +3 -0
  177. package/dist/collections/layers.js +77 -0
  178. package/dist/collections/metrics.d.ts +3 -0
  179. package/dist/collections/metrics.js +115 -0
  180. package/dist/collections/phases.d.ts +3 -0
  181. package/dist/collections/phases.js +59 -0
  182. package/dist/collections/serialize.d.ts +5 -0
  183. package/dist/collections/serialize.js +40 -0
  184. package/dist/collections/snapshots.d.ts +3 -0
  185. package/dist/collections/snapshots.js +47 -0
  186. package/dist/constants.d.ts +7 -0
  187. package/dist/constants.js +7 -0
  188. package/dist/db/adapter.d.ts +3 -0
  189. package/dist/db/adapter.js +9 -0
  190. package/dist/db/appends.d.ts +58 -0
  191. package/dist/db/appends.js +116 -0
  192. package/dist/db/delete.d.ts +17 -0
  193. package/dist/db/delete.js +35 -0
  194. package/dist/db/erase.d.ts +12 -0
  195. package/dist/db/erase.js +55 -0
  196. package/dist/db/events.d.ts +21 -0
  197. package/dist/db/events.js +100 -0
  198. package/dist/db/executor.d.ts +10 -0
  199. package/dist/db/executor.js +37 -0
  200. package/dist/db/identities.d.ts +11 -0
  201. package/dist/db/identities.js +29 -0
  202. package/dist/db/locks.d.ts +15 -0
  203. package/dist/db/locks.js +23 -0
  204. package/dist/db/manifest.d.ts +8 -0
  205. package/dist/db/manifest.js +8 -0
  206. package/dist/db/names.d.ts +271 -0
  207. package/dist/db/names.js +270 -0
  208. package/dist/db/pgExecutor.d.ts +41 -0
  209. package/dist/db/pgExecutor.js +250 -0
  210. package/dist/db/results.d.ts +53 -0
  211. package/dist/db/results.js +103 -0
  212. package/dist/db/slices.d.ts +18 -0
  213. package/dist/db/slices.js +51 -0
  214. package/dist/db/snapshots.d.ts +47 -0
  215. package/dist/db/snapshots.js +89 -0
  216. package/dist/db/sql.d.ts +5 -0
  217. package/dist/db/sql.js +5 -0
  218. package/dist/db/state.d.ts +12 -0
  219. package/dist/db/state.js +27 -0
  220. package/dist/db/sweep.d.ts +13 -0
  221. package/dist/db/sweep.js +90 -0
  222. package/dist/db/txid.d.ts +7 -0
  223. package/dist/db/txid.js +12 -0
  224. package/dist/define.d.ts +3 -0
  225. package/dist/define.js +6 -0
  226. package/dist/endpoints/events.d.ts +7 -0
  227. package/dist/endpoints/events.js +98 -0
  228. package/dist/endpoints/index.d.ts +18 -0
  229. package/dist/endpoints/index.js +160 -0
  230. package/dist/endpoints/manifest.d.ts +7 -0
  231. package/dist/endpoints/manifest.js +30 -0
  232. package/dist/endpoints/preflight.d.ts +8 -0
  233. package/dist/endpoints/preflight.js +25 -0
  234. package/dist/endpoints/previewToken.d.ts +7 -0
  235. package/dist/endpoints/previewToken.js +54 -0
  236. package/dist/endpoints/recompute.d.ts +7 -0
  237. package/dist/endpoints/recompute.js +41 -0
  238. package/dist/endpoints/results.d.ts +24 -0
  239. package/dist/endpoints/results.js +136 -0
  240. package/dist/endpoints/summary.d.ts +7 -0
  241. package/dist/endpoints/summary.js +40 -0
  242. package/dist/endpoints/transitions.d.ts +42 -0
  243. package/dist/endpoints/transitions.js +154 -0
  244. package/dist/errors.d.ts +27 -0
  245. package/dist/errors.js +33 -0
  246. package/dist/events/body.d.ts +7 -0
  247. package/dist/events/body.js +43 -0
  248. package/dist/events/dimensions.d.ts +4 -0
  249. package/dist/events/dimensions.js +51 -0
  250. package/dist/events/preview.d.ts +2 -0
  251. package/dist/events/preview.js +9 -0
  252. package/dist/events/request.d.ts +3 -0
  253. package/dist/events/request.js +14 -0
  254. package/dist/events/rows.d.ts +7 -0
  255. package/dist/events/rows.js +65 -0
  256. package/dist/exports/adminClient.d.ts +16 -0
  257. package/dist/exports/adminClient.js +17 -0
  258. package/dist/exports/client.d.ts +7 -0
  259. package/dist/exports/client.js +7 -0
  260. package/dist/exports/index.d.ts +4 -0
  261. package/dist/exports/index.js +3 -0
  262. package/dist/exports/proxy.d.ts +4 -0
  263. package/dist/exports/proxy.js +3 -0
  264. package/dist/exports/puck.d.ts +4 -0
  265. package/dist/exports/puck.js +3 -0
  266. package/dist/exports/puckEditor.d.ts +1 -0
  267. package/dist/exports/puckEditor.js +2 -0
  268. package/dist/exports/rsc.d.ts +13 -0
  269. package/dist/exports/rsc.js +13 -0
  270. package/dist/exports/runtimeProxy.d.ts +4 -0
  271. package/dist/exports/runtimeProxy.js +2 -0
  272. package/dist/exports/runtimeServer.d.ts +2 -0
  273. package/dist/exports/runtimeServer.js +1 -0
  274. package/dist/fields/armSelect.d.ts +7 -0
  275. package/dist/fields/armSelect.js +33 -0
  276. package/dist/globals/state.d.ts +3 -0
  277. package/dist/globals/state.js +31 -0
  278. package/dist/hooks/contentChanges.d.ts +3 -0
  279. package/dist/hooks/contentChanges.js +10 -0
  280. package/dist/hooks/hostHook.d.ts +5 -0
  281. package/dist/hooks/hostHook.js +20 -0
  282. package/dist/index.d.ts +1 -0
  283. package/dist/index.js +4 -0
  284. package/dist/manifest/cache.d.ts +10 -0
  285. package/dist/manifest/cache.js +51 -0
  286. package/dist/manifest/compile.d.ts +63 -0
  287. package/dist/manifest/compile.js +237 -0
  288. package/dist/onInit.d.ts +5 -0
  289. package/dist/onInit.js +13 -0
  290. package/dist/options/fromReq.d.ts +3 -0
  291. package/dist/options/fromReq.js +5 -0
  292. package/dist/options.d.ts +9 -0
  293. package/dist/options.js +99 -0
  294. package/dist/plugin.d.ts +7 -0
  295. package/dist/plugin.js +62 -0
  296. package/dist/proxy/apply.d.ts +3 -0
  297. package/dist/proxy/apply.js +5 -0
  298. package/dist/proxy/consentFallback.d.ts +5 -0
  299. package/dist/proxy/consentFallback.js +27 -0
  300. package/dist/proxy/control.d.ts +6 -0
  301. package/dist/proxy/control.js +36 -0
  302. package/dist/proxy/createProxy.d.ts +3 -0
  303. package/dist/proxy/createProxy.js +15 -0
  304. package/dist/proxy/pageRewrite.d.ts +4 -0
  305. package/dist/proxy/pageRewrite.js +41 -0
  306. package/dist/proxy/resolve.d.ts +13 -0
  307. package/dist/proxy/resolve.js +163 -0
  308. package/dist/proxy/skip.d.ts +1 -0
  309. package/dist/proxy/skip.js +9 -0
  310. package/dist/puck/ExperimentPicker.d.ts +5 -0
  311. package/dist/puck/ExperimentPicker.js +88 -0
  312. package/dist/puck/ExperimentSlot.d.ts +3 -0
  313. package/dist/puck/ExperimentSlot.js +16 -0
  314. package/dist/puck/copy.d.ts +14 -0
  315. package/dist/puck/copy.js +14 -0
  316. package/dist/puck/editor/CreateExperimentDrawer.d.ts +5 -0
  317. package/dist/puck/editor/CreateExperimentDrawer.js +120 -0
  318. package/dist/puck/editor/ExperimentSlotEditor.d.ts +3 -0
  319. package/dist/puck/editor/ExperimentSlotEditor.js +225 -0
  320. package/dist/puck/editor/cloneSlot.d.ts +3 -0
  321. package/dist/puck/editor/cloneSlot.js +41 -0
  322. package/dist/puck/editor/copy.d.ts +25 -0
  323. package/dist/puck/editor/copy.js +25 -0
  324. package/dist/puck/editor/editor.model.d.ts +16 -0
  325. package/dist/puck/editor/editor.model.js +33 -0
  326. package/dist/puck/experimentSlotField.d.ts +5 -0
  327. package/dist/puck/experimentSlotField.js +13 -0
  328. package/dist/puck/slot.model.d.ts +2 -0
  329. package/dist/puck/slot.model.js +11 -0
  330. package/dist/puck/slotConfig.d.ts +5 -0
  331. package/dist/puck/slotConfig.js +25 -0
  332. package/dist/results/aggregate.d.ts +34 -0
  333. package/dist/results/aggregate.js +66 -0
  334. package/dist/results/analyse.d.ts +79 -0
  335. package/dist/results/analyse.js +378 -0
  336. package/dist/results/cuped.d.ts +16 -0
  337. package/dist/results/cuped.js +31 -0
  338. package/dist/results/decideGuard.d.ts +3 -0
  339. package/dist/results/decideGuard.js +8 -0
  340. package/dist/results/guardrails.d.ts +17 -0
  341. package/dist/results/guardrails.js +10 -0
  342. package/dist/results/history.d.ts +13 -0
  343. package/dist/results/history.js +39 -0
  344. package/dist/results/identityCredit.d.ts +16 -0
  345. package/dist/results/identityCredit.js +46 -0
  346. package/dist/results/power.d.ts +15 -0
  347. package/dist/results/power.js +60 -0
  348. package/dist/results/rolloutUnits.d.ts +17 -0
  349. package/dist/results/rolloutUnits.js +26 -0
  350. package/dist/results/snapshot.d.ts +9 -0
  351. package/dist/results/snapshot.js +154 -0
  352. package/dist/results/srm.d.ts +9 -0
  353. package/dist/results/srm.js +22 -0
  354. package/dist/results/verdict.d.ts +82 -0
  355. package/dist/results/verdict.js +85 -0
  356. package/dist/runtime/assign.d.ts +23 -0
  357. package/dist/runtime/assign.js +104 -0
  358. package/dist/runtime/bot.d.ts +1 -0
  359. package/dist/runtime/bot.js +7 -0
  360. package/dist/runtime/cookie.d.ts +17 -0
  361. package/dist/runtime/cookie.js +35 -0
  362. package/dist/runtime/eligibility.d.ts +22 -0
  363. package/dist/runtime/eligibility.js +20 -0
  364. package/dist/runtime/header.d.ts +17 -0
  365. package/dist/runtime/header.js +97 -0
  366. package/dist/runtime/loaders.d.ts +17 -0
  367. package/dist/runtime/loaders.js +29 -0
  368. package/dist/runtime/normalisePath.d.ts +7 -0
  369. package/dist/runtime/normalisePath.js +15 -0
  370. package/dist/runtime/previewToken.d.ts +9 -0
  371. package/dist/runtime/previewToken.js +23 -0
  372. package/dist/runtime/proxyRuntime.d.ts +2 -0
  373. package/dist/runtime/proxyRuntime.js +22 -0
  374. package/dist/runtime/seed.d.ts +2 -0
  375. package/dist/runtime/seed.js +4 -0
  376. package/dist/runtime/signing.d.ts +3 -0
  377. package/dist/runtime/signing.js +16 -0
  378. package/dist/runtime/targeting.d.ts +6 -0
  379. package/dist/runtime/targeting.js +9 -0
  380. package/dist/server/context.d.ts +10 -0
  381. package/dist/server/context.js +59 -0
  382. package/dist/server/eraseUser.d.ts +7 -0
  383. package/dist/server/eraseUser.js +4 -0
  384. package/dist/server/getAssignments.d.ts +9 -0
  385. package/dist/server/getAssignments.js +227 -0
  386. package/dist/server/getFlagPoint.d.ts +11 -0
  387. package/dist/server/getFlagPoint.js +36 -0
  388. package/dist/server/headerRevision.d.ts +6 -0
  389. package/dist/server/headerRevision.js +10 -0
  390. package/dist/server/linkIdentity.d.ts +9 -0
  391. package/dist/server/linkIdentity.js +34 -0
  392. package/dist/server/metadata.d.ts +7 -0
  393. package/dist/server/metadata.js +24 -0
  394. package/dist/server/previewDraft.d.ts +7 -0
  395. package/dist/server/previewDraft.js +51 -0
  396. package/dist/server/recordContentChange.d.ts +7 -0
  397. package/dist/server/recordContentChange.js +150 -0
  398. package/dist/server/recordExposures.d.ts +7 -0
  399. package/dist/server/recordExposures.js +56 -0
  400. package/dist/server/request.d.ts +7 -0
  401. package/dist/server/request.js +39 -0
  402. package/dist/server/resolveExperimentData.d.ts +6 -0
  403. package/dist/server/resolveExperimentData.js +64 -0
  404. package/dist/server/runResults.d.ts +22 -0
  405. package/dist/server/runResults.js +307 -0
  406. package/dist/server/serverRuntime.d.ts +7 -0
  407. package/dist/server/serverRuntime.js +20 -0
  408. package/dist/server/sitemap.d.ts +3 -0
  409. package/dist/server/sitemap.js +6 -0
  410. package/dist/server/toClientAssignments.d.ts +7 -0
  411. package/dist/server/toClientAssignments.js +56 -0
  412. package/dist/server/trackConversion.d.ts +12 -0
  413. package/dist/server/trackConversion.js +69 -0
  414. package/dist/targeting/ops.d.ts +8 -0
  415. package/dist/targeting/ops.js +58 -0
  416. package/dist/transitions/addMetric.d.ts +16 -0
  417. package/dist/transitions/addMetric.js +126 -0
  418. package/dist/transitions/allocate.d.ts +9 -0
  419. package/dist/transitions/allocate.js +84 -0
  420. package/dist/transitions/archive.d.ts +9 -0
  421. package/dist/transitions/archive.js +46 -0
  422. package/dist/transitions/checkStart.d.ts +61 -0
  423. package/dist/transitions/checkStart.js +104 -0
  424. package/dist/transitions/confirmPhrases.d.ts +5 -0
  425. package/dist/transitions/confirmPhrases.js +18 -0
  426. package/dist/transitions/decide.d.ts +9 -0
  427. package/dist/transitions/decide.js +138 -0
  428. package/dist/transitions/deleteExperiment.d.ts +5 -0
  429. package/dist/transitions/deleteExperiment.js +66 -0
  430. package/dist/transitions/kernel.d.ts +31 -0
  431. package/dist/transitions/kernel.js +69 -0
  432. package/dist/transitions/makeContent.d.ts +20 -0
  433. package/dist/transitions/makeContent.js +411 -0
  434. package/dist/transitions/phaseRecord.d.ts +27 -0
  435. package/dist/transitions/phaseRecord.js +81 -0
  436. package/dist/transitions/placements.d.ts +26 -0
  437. package/dist/transitions/placements.js +89 -0
  438. package/dist/transitions/preflightData.d.ts +21 -0
  439. package/dist/transitions/preflightData.js +317 -0
  440. package/dist/transitions/releaseHoldout.d.ts +8 -0
  441. package/dist/transitions/releaseHoldout.js +30 -0
  442. package/dist/transitions/resolveCaps.d.ts +28 -0
  443. package/dist/transitions/resolveCaps.js +68 -0
  444. package/dist/transitions/start.d.ts +26 -0
  445. package/dist/transitions/start.js +220 -0
  446. package/dist/transitions/stop.d.ts +8 -0
  447. package/dist/transitions/stop.js +25 -0
  448. package/dist/types/admin.d.ts +20 -0
  449. package/dist/types/admin.js +1 -0
  450. package/dist/types/assignments.d.ts +57 -0
  451. package/dist/types/assignments.js +1 -0
  452. package/dist/types/contracts.d.ts +133 -0
  453. package/dist/types/contracts.js +2 -0
  454. package/dist/types/endpoints.d.ts +135 -0
  455. package/dist/types/endpoints.js +1 -0
  456. package/dist/types/index.d.ts +8 -0
  457. package/dist/types/index.js +8 -0
  458. package/dist/types/manifest.d.ts +42 -0
  459. package/dist/types/manifest.js +1 -0
  460. package/dist/types/puck.d.ts +23 -0
  461. package/dist/types/puck.js +1 -0
  462. package/dist/types/runtime.d.ts +59 -0
  463. package/dist/types/runtime.js +1 -0
  464. package/dist/types/snapshot.d.ts +117 -0
  465. package/dist/types/snapshot.js +1 -0
  466. package/package.json +137 -3
@@ -0,0 +1,378 @@
1
+ import { DEFAULT_GAUSSIAN_PRIOR, bonferroniAlpha, capValues, createThetaAdjustedStatistics, effectBayesianABTest, sequentialOneSidedTreatmentGreaterTTest, sequentialOneSidedTreatmentLesserTTest, sequentialTwoSidedTTest, statisticMean, statisticVariance } from '@delmaredigital/experiments-stats';
2
+ import { timestampMs } from './aggregate.js';
3
+ import { outcomeStatistic } from './cuped.js';
4
+ import { guardrailStatus } from './guardrails.js';
5
+ import { remainingPower } from './power.js';
6
+ import { assembleSnapshot, jsonInterval } from './snapshot.js';
7
+ import { exposureSrm } from './srm.js';
8
+ import { computeVerdict } from './verdict.js';
9
+ const severity = {
10
+ ok: 0,
11
+ insufficient: 1,
12
+ warning: 2,
13
+ failing: 3
14
+ };
15
+ /** Pure exposure-based analysis (§6, §7.6, §8.4). All inference, moments, capping,
16
+ * CUPED, power and SRM are delegated to experiments-stats; now is always injected.
17
+ */ export function analysePhase(input) {
18
+ const { phase, units } = input;
19
+ const { analysis } = phase;
20
+ const alpha = analysis.alpha ?? 0.05;
21
+ const prior = analysis.prior ?? DEFAULT_GAUSSIAN_PRIOR;
22
+ const bayesian = analysis.bayesian ?? {
23
+ ctw: 0.95,
24
+ risk: 0.0125
25
+ };
26
+ const rollout = phase.phaseKind === 'rollout';
27
+ const baselineArm = phase.arms.find((arm)=>arm.kind === (rollout ? 'holdout' : 'control'));
28
+ const winner = rollout ? phase.arms.find((arm)=>arm.key === input.winnerArm && arm.kind !== 'holdout') : undefined;
29
+ if (!baselineArm || rollout && (!winner || phase.arms.length !== 2)) throw new RangeError('Phase comparison baseline or shipped arm is missing');
30
+ const compared = phase.arms.filter((arm)=>rollout ? arm.key === winner.key : arm.kind === 'variant');
31
+ const k = compared.length;
32
+ const alphaAdjusted = analysis.method === 'sequential' ? bonferroniAlpha(alpha, k) : alpha;
33
+ const definitions = phase.metricDefinitions.map((metric)=>({
34
+ ...metric,
35
+ attributionWindowHours: analysis.attributionWindowHours ?? metric.attributionWindowHours,
36
+ minSampleSize: analysis.minSampleSizes?.[metric.metricId] ?? metric.minSampleSize,
37
+ guardrailThreshold: analysis.guardrailThresholds?.[metric.metricId] ?? metric.guardrailThreshold
38
+ }));
39
+ const primaryDefinition = definitions.find((metric)=>metric.role === 'primary');
40
+ if (!primaryDefinition || phase.metricDefinitions.filter((metric)=>metric.role === 'primary').length !== 1) {
41
+ throw new RangeError('Phase must have exactly one primary metric');
42
+ }
43
+ if (new Set(phase.metricDefinitions.map((metric)=>String(metric.metricId))).size !== phase.metricDefinitions.length || new Set(input.metricData.map((metric)=>String(metric.metricId))).size !== input.metricData.length) {
44
+ throw new RangeError('Duplicate metric id');
45
+ }
46
+ const from = timestampMs(phase.startedAt);
47
+ const now = timestampMs(input.now);
48
+ const to = Math.min(now, phase.endedAt == null ? now : timestampMs(phase.endedAt));
49
+ if (to < from) throw new RangeError('Analysis window ends before the phase starts');
50
+ const byArm = new Map(phase.arms.map((arm)=>[
51
+ arm.key,
52
+ []
53
+ ]));
54
+ for (const [id, unit] of units){
55
+ const ids = byArm.get(unit.arm);
56
+ if (!ids) throw new RangeError('Exposed unit has an unknown arm');
57
+ ids.push(id);
58
+ }
59
+ const perArm = Object.fromEntries([
60
+ ...byArm
61
+ ].map(([arm, ids])=>[
62
+ arm,
63
+ ids.length
64
+ ]));
65
+ const excludedMultiArm = input.excludedMultiArm ?? 0;
66
+ const exposures = {
67
+ total: units.size,
68
+ perArm,
69
+ excludedMultiArm,
70
+ multiArmShare: units.size + excludedMultiArm === 0 ? 0 : excludedMultiArm / (units.size + excludedMultiArm),
71
+ armMismatch: input.armMismatch ?? 0,
72
+ identityConflictShare: input.identityConflictShare ?? 0,
73
+ capped: {}
74
+ };
75
+ const params = {
76
+ sequentialTuningParameter: analysis.sequentialTuningParameter,
77
+ prior: {
78
+ ...prior
79
+ },
80
+ cupedLookbackDays: analysis.cupedLookbackDays ?? 14,
81
+ bayesian: {
82
+ ...bayesian
83
+ },
84
+ caps: {},
85
+ attributionWindows: {},
86
+ guardrailThresholds: {},
87
+ minSampleSizes: {}
88
+ };
89
+ const guardrails = [];
90
+ const verdictGuardrails = [];
91
+ let primaryStatistics;
92
+ const metrics = definitions.map((definition)=>{
93
+ const { metricId, kind } = definition;
94
+ params.attributionWindows[metricId] = definition.attributionWindowHours;
95
+ params.minSampleSizes[metricId] = definition.minSampleSize;
96
+ if (definition.role === 'guardrail') params.guardrailThresholds[metricId] = definition.guardrailThreshold ?? 0.05;
97
+ if (kind === 'revenue' && definition.capping) params.caps[metricId] = {
98
+ ...definition.capping,
99
+ resolvedCap: definition.resolvedCap ?? null,
100
+ ...definition.historyUnits === undefined ? {} : {
101
+ historyUnits: definition.historyUnits
102
+ }
103
+ };
104
+ exposures.capped[metricId] = 0;
105
+ const data = input.metricData.find((item)=>item.metricId === metricId);
106
+ const metric = {
107
+ metricId,
108
+ name: definition.name,
109
+ kind,
110
+ role: definition.role,
111
+ cupedApplied: false,
112
+ perArm: [],
113
+ comparisons: []
114
+ };
115
+ let worst = 'ok';
116
+ let worstArmLabel = compared[0]?.label ?? '';
117
+ try {
118
+ if (!data || data.unavailable !== undefined) throw new Error(data?.unavailable ?? 'Metric data was not supplied');
119
+ const stats = new Map();
120
+ const cap = kind === 'revenue' ? definition.resolvedCap ?? null : null;
121
+ const useCovariates = analysis.cuped === 'auto' && data.covariates !== undefined && data.covariates.size > 0;
122
+ for (const arm of phase.arms){
123
+ const ids = byArm.get(arm.key);
124
+ const values = ids.map((id)=>data.values.get(id) ?? 0);
125
+ if (!values.every(Number.isFinite)) throw new RangeError('Metric values must be finite');
126
+ const capped = capValues(values, cap);
127
+ const rawPre = useCovariates ? ids.map((id)=>data.covariates.get(id) ?? 0) : undefined;
128
+ if (rawPre && !rawPre.every(Number.isFinite)) throw new RangeError('Covariates must be finite');
129
+ const pre = rawPre && capValues(rawPre, cap).values;
130
+ const built = outcomeStatistic(kind, capped.values, pre);
131
+ stats.set(arm.key, built.statistic);
132
+ exposures.capped[metricId] += capped.cappedCount;
133
+ // These are the observed post-period moments; pair-specific theta is
134
+ // applied by the stats engine for inference, never to the displayed mean.
135
+ const post = outcomeStatistic(kind, capped.values).statistic;
136
+ metric.perArm.push({
137
+ arm: arm.key,
138
+ ...built.moments,
139
+ converters: values.filter((value)=>value > 0).length,
140
+ mean: statisticMean(post),
141
+ variance: statisticVariance(post),
142
+ ...built.covariate ? {
143
+ covariate: built.covariate
144
+ } : {},
145
+ ...cap === null ? {} : {
146
+ capped: {
147
+ resolvedCap: cap,
148
+ uncappedTotal: capped.uncappedTotal,
149
+ cappedCount: capped.cappedCount
150
+ }
151
+ }
152
+ });
153
+ }
154
+ if (definition.role === 'primary') primaryStatistics = stats;
155
+ for (const arm of compared){
156
+ const [a, b] = createThetaAdjustedStatistics(stats.get(baselineArm.key), stats.get(arm.key));
157
+ metric.cupedApplied ||= a.type === 'regressionAdjusted' && b.type === 'regressionAdjusted';
158
+ const config = {
159
+ alpha,
160
+ sequentialTuningParameter: analysis.sequentialTuningParameter
161
+ };
162
+ const inverse = definition.direction === 'decrease';
163
+ let comparison;
164
+ let status = 'insufficient';
165
+ if (analysis.method === 'sequential') {
166
+ const raw = sequentialTwoSidedTTest(a, b, config);
167
+ const adjusted = definition.role === 'primary' ? sequentialTwoSidedTTest(a, b, {
168
+ ...config,
169
+ alpha: alphaAdjusted
170
+ }) : raw;
171
+ const ci = adjusted.errorMessage ? [
172
+ null,
173
+ null
174
+ ] : jsonInterval(adjusted.ci);
175
+ const positive = ci[0] !== null && ci[0] > 0;
176
+ const negative = ci[1] !== null && ci[1] < 0;
177
+ comparison = {
178
+ arm: arm.key,
179
+ lift: raw.expected,
180
+ ci: raw.errorMessage ? [
181
+ null,
182
+ null
183
+ ] : jsonInterval(raw.ci),
184
+ ciAdjusted: ci,
185
+ ...raw.pValue === null ? {} : {
186
+ pValue: raw.pValue
187
+ },
188
+ ...adjusted.pValue === null ? {} : {
189
+ pValueAdjusted: Math.min(1, adjusted.pValue * (definition.role === 'primary' ? k : 1))
190
+ },
191
+ significant: positive ? inverse ? 'worse' : 'better' : negative ? inverse ? 'better' : 'worse' : 'none'
192
+ };
193
+ if (definition.role === 'guardrail') {
194
+ const greater = sequentialOneSidedTreatmentGreaterTTest(a, b, config);
195
+ const lesser = sequentialOneSidedTreatmentLesserTTest(a, b, config);
196
+ status = guardrailStatus({
197
+ method: 'sequential',
198
+ direction: definition.direction,
199
+ n: Math.min(...metric.perArm.filter((group)=>group.arm === arm.key || group.arm === baselineArm.key).map((group)=>group.converters)),
200
+ minSampleSize: definition.minSampleSize,
201
+ threshold: definition.guardrailThreshold ?? 0.05,
202
+ pointEstimate: raw.expected,
203
+ lowerBound: greater.errorMessage ? null : jsonInterval(greater.ci)[0],
204
+ upperBound: lesser.errorMessage ? null : jsonInterval(lesser.ci)[1]
205
+ });
206
+ }
207
+ } else {
208
+ const result = effectBayesianABTest(a, b, {
209
+ alpha,
210
+ priorEffect: prior,
211
+ inverse
212
+ });
213
+ comparison = {
214
+ arm: arm.key,
215
+ lift: result.expected,
216
+ ci: result.errorMessage ? [
217
+ null,
218
+ null
219
+ ] : jsonInterval(result.ci),
220
+ ciAdjusted: result.errorMessage ? [
221
+ null,
222
+ null
223
+ ] : jsonInterval(result.ci),
224
+ chanceToWin: result.chanceToWin,
225
+ risk: result.risk[1],
226
+ significant: result.errorMessage ? 'none' : result.chanceToWin >= bayesian.ctw ? 'better' : result.chanceToWin <= 1 - bayesian.ctw ? 'worse' : 'none'
227
+ };
228
+ if (definition.role === 'guardrail') {
229
+ const safety = effectBayesianABTest(a, b, {
230
+ alpha: 0.10,
231
+ priorEffect: prior,
232
+ inverse
233
+ });
234
+ status = guardrailStatus({
235
+ method: 'bayesian',
236
+ direction: definition.direction,
237
+ n: Math.min(...metric.perArm.filter((group)=>group.arm === arm.key || group.arm === baselineArm.key).map((group)=>group.converters)),
238
+ minSampleSize: definition.minSampleSize,
239
+ threshold: definition.guardrailThreshold ?? 0.05,
240
+ pointEstimate: result.expected,
241
+ lowerBound: safety.errorMessage ? null : jsonInterval(safety.ci)[0],
242
+ upperBound: safety.errorMessage ? null : jsonInterval(safety.ci)[1],
243
+ pWorse: safety.errorMessage ? undefined : 1 - safety.chanceToWin
244
+ });
245
+ }
246
+ }
247
+ metric.comparisons.push(comparison);
248
+ if (severity[status] > severity[worst]) {
249
+ worst = status;
250
+ worstArmLabel = arm.label;
251
+ }
252
+ }
253
+ } catch (error) {
254
+ metric.unavailable = error instanceof Error ? error.message : 'Metric could not be analysed';
255
+ metric.perArm = [];
256
+ metric.comparisons = [];
257
+ metric.cupedApplied = false;
258
+ exposures.capped[metricId] = 0;
259
+ if (definition.role === 'primary') primaryStatistics = undefined;
260
+ worst = 'insufficient';
261
+ }
262
+ if (definition.role === 'guardrail') {
263
+ guardrails.push({
264
+ metricId,
265
+ status: worst
266
+ });
267
+ verdictGuardrails.push(worst === 'failing' ? {
268
+ name: definition.name,
269
+ status: worst,
270
+ worstArmLabel
271
+ } : {
272
+ name: definition.name,
273
+ status: worst
274
+ });
275
+ }
276
+ return metric;
277
+ });
278
+ const primary = metrics.find((metric)=>metric.role === 'primary');
279
+ const srm = exposureSrm(phase.arms, perArm);
280
+ let power = {
281
+ daysRemaining: null,
282
+ exposuresNeededPerArm: null,
283
+ lowerBound: analysis.method === 'sequential',
284
+ feasible: true
285
+ };
286
+ const common = {
287
+ status: input.status,
288
+ method: analysis.method,
289
+ primary: {
290
+ name: primary.name,
291
+ direction: primaryDefinition.direction,
292
+ ...primary.unavailable === undefined ? {} : {
293
+ unavailable: primary.unavailable
294
+ },
295
+ minSampleSize: primaryDefinition.minSampleSize,
296
+ convertersPerArm: primary.perArm.map((group)=>({
297
+ arm: group.arm,
298
+ kind: phase.arms.find((arm)=>arm.key === group.arm).kind,
299
+ converters: group.converters
300
+ })),
301
+ comparisons: primary.comparisons.map((comparison)=>({
302
+ ...comparison,
303
+ ci: comparison.ciAdjusted,
304
+ label: phase.arms.find((arm)=>arm.key === comparison.arm).label
305
+ }))
306
+ },
307
+ exposures: {
308
+ perArm: phase.arms.map((arm)=>({
309
+ arm: arm.key,
310
+ label: arm.label,
311
+ kind: arm.kind,
312
+ n: perArm[arm.key]
313
+ })),
314
+ minExposuresPerArm: analysis.minExposuresPerArm,
315
+ multiArmShare: exposures.multiArmShare
316
+ },
317
+ srm,
318
+ guardrails: verdictGuardrails,
319
+ power,
320
+ plannedDurationPassed: to - from >= analysis.plannedDurationDays * 86_400_000,
321
+ originalLabel: phase.arms.find((arm)=>arm.kind === 'control')?.label ?? 'Original',
322
+ controlArm: baselineArm.key
323
+ };
324
+ const verdictInput = rollout ? {
325
+ ...common,
326
+ phaseKind: 'rollout',
327
+ winnerArm: winner.key,
328
+ winnerLabel: winner.label
329
+ } : {
330
+ ...common,
331
+ phaseKind: 'test'
332
+ };
333
+ let verdict = computeVerdict(verdictInput);
334
+ if (input.status === 'running' && !rollout && (verdict.row === 4 || verdict.row === 9) && primaryStatistics) {
335
+ power = remainingPower({
336
+ baseline: primaryStatistics.get(baselineArm.key),
337
+ variants: compared.map((arm)=>primaryStatistics.get(arm.key)),
338
+ elapsedDays: (to - from) / 86_400_000,
339
+ alpha: alphaAdjusted,
340
+ method: analysis.method,
341
+ prior,
342
+ sequentialTuningParameter: analysis.sequentialTuningParameter
343
+ });
344
+ verdict = computeVerdict({
345
+ ...verdictInput,
346
+ power
347
+ });
348
+ }
349
+ return assembleSnapshot({
350
+ experiment: phase.experiment,
351
+ phase: phase.phase,
352
+ phaseKind: phase.phaseKind,
353
+ computedAt: new Date(now).toISOString(),
354
+ window: {
355
+ from: new Date(from).toISOString(),
356
+ to: new Date(to).toISOString()
357
+ },
358
+ method: analysis.method,
359
+ alpha,
360
+ alphaAdjusted,
361
+ k,
362
+ winnerArm: winner?.key ?? null,
363
+ minExposuresPerArm: analysis.minExposuresPerArm,
364
+ params,
365
+ exposures,
366
+ srm,
367
+ metrics,
368
+ guardrails,
369
+ power,
370
+ verdict,
371
+ contentChanges: (phase.contentChanges ?? []).map((change)=>({
372
+ ...change,
373
+ doc: {
374
+ ...change.doc
375
+ }
376
+ }))
377
+ });
378
+ }
@@ -0,0 +1,16 @@
1
+ import { summarizeUnits } from '@delmaredigital/experiments-stats';
2
+ import type { TestStatistic } from '@delmaredigital/experiments-stats';
3
+ import type { MetricSource } from '../types/contracts.js';
4
+ /** Build candidate CUPED statistics; the stats engine pools each pair, infers theta,
5
+ * and falls back to the post statistic when pre-period variance/covariance is zero.
6
+ * Missing units in a supplied pre-period map are zero-filled by the caller.
7
+ */
8
+ export declare function outcomeStatistic(kind: MetricSource['kind'], values: readonly number[], covariates?: readonly number[]): {
9
+ statistic: TestStatistic;
10
+ moments: ReturnType<typeof summarizeUnits>;
11
+ covariate?: {
12
+ preSum: number;
13
+ preSumSquares: number;
14
+ postPreSumOfProducts: number;
15
+ };
16
+ };
@@ -0,0 +1,31 @@
1
+ import { buildRegressionAdjustedStatistic, buildStatistic, summarizeUnits, summarizeUnitsWithCovariate } from '@delmaredigital/experiments-stats';
2
+ /** Build candidate CUPED statistics; the stats engine pools each pair, infers theta,
3
+ * and falls back to the post statistic when pre-period variance/covariance is zero.
4
+ * Missing units in a supplied pre-period map are zero-filled by the caller.
5
+ */ export function outcomeStatistic(kind, values, covariates) {
6
+ if (!values.every(Number.isFinite) || covariates && !covariates.every(Number.isFinite)) {
7
+ throw new RangeError('Metric values and covariates must be finite');
8
+ }
9
+ if (kind === 'binomial' && (values.some((value)=>value !== 0 && value !== 1) || covariates?.some((value)=>value !== 0 && value !== 1))) {
10
+ throw new RangeError('Binomial values and covariates must be 0 or 1');
11
+ }
12
+ const moments = summarizeUnits(values);
13
+ if (covariates === undefined) return {
14
+ moments,
15
+ statistic: buildStatistic(kind, moments)
16
+ };
17
+ const { preSum, preSumSquares, postPreSumOfProducts } = summarizeUnitsWithCovariate(values, covariates);
18
+ const covariate = {
19
+ preSum,
20
+ preSumSquares,
21
+ postPreSumOfProducts
22
+ };
23
+ return {
24
+ moments,
25
+ covariate,
26
+ statistic: buildRegressionAdjustedStatistic(kind, {
27
+ ...moments,
28
+ ...covariate
29
+ })
30
+ };
31
+ }
@@ -0,0 +1,3 @@
1
+ import type { Snapshot } from '../types/snapshot.js';
2
+ /** A true result means required: true: typed confirmation and a note are both required (plan §2.7). */
3
+ export declare function decideGuard(snapshot: Snapshot | null, chosenArm: string): boolean;
@@ -0,0 +1,8 @@
1
+ /** A true result means required: true: typed confirmation and a note are both required (plan §2.7). */ export function decideGuard(snapshot, chosenArm) {
2
+ return snapshot === null || snapshot.verdict.recommendedArm === null || chosenArm !== snapshot.verdict.recommendedArm || [
3
+ 1,
4
+ 2,
5
+ 3,
6
+ 4
7
+ ].some((row)=>row === snapshot.verdict.row);
8
+ }
@@ -0,0 +1,17 @@
1
+ export interface GuardrailInput {
2
+ method: 'sequential' | 'bayesian';
3
+ n: number;
4
+ minSampleSize: number;
5
+ threshold: number;
6
+ /** Metric collection direction, passed through without translation. */
7
+ direction: 'increase' | 'decrease';
8
+ /** Raw relative-effect bounds: sequential greater-test lower and lesser-test upper.
9
+ * Bayesian bounds use alpha 0.10; pass raw bounds before direction inversion. */
10
+ lowerBound: number | null;
11
+ upperBound: number | null;
12
+ pointEstimate: number;
13
+ /** Probability of harm, already adjusted for direction. */
14
+ pWorse?: number;
15
+ }
16
+ /** Classify precomputed one-sided results in the order specified by plan CORE-15a. */
17
+ export declare function guardrailStatus(input: GuardrailInput): 'failing' | 'ok' | 'warning' | 'insufficient';
@@ -0,0 +1,10 @@
1
+ /** Classify precomputed one-sided results in the order specified by plan CORE-15a. */ export function guardrailStatus(input) {
2
+ const inverse = input.direction === 'decrease';
3
+ const lower = inverse ? input.upperBound === null ? null : -input.upperBound : input.lowerBound;
4
+ const upper = inverse ? input.lowerBound === null ? null : -input.lowerBound : input.upperBound;
5
+ const harmful = input.method === 'bayesian' ? (input.pWorse ?? 0) >= 0.95 : upper !== null && upper < 0;
6
+ if (input.n >= input.minSampleSize && harmful) return 'failing';
7
+ if (lower !== null && lower > -input.threshold) return 'ok';
8
+ const warning = input.method === 'bayesian' ? (input.pWorse ?? 0) >= 0.70 : (inverse ? -input.pointEstimate : input.pointEstimate) < 0;
9
+ return warning ? 'warning' : 'insufficient';
10
+ }
@@ -0,0 +1,13 @@
1
+ import type { MetricEvent } from './aggregate.js';
2
+ /**
3
+ * Spec §5.4: "Default for revenue sources is `percentile 0.99`; the threshold is resolved at Start from the source's own `query({ from: start − 90 d, to: start })` (no extra host contract): events are aggregated **per unit** in **repeated greedy windows**: the unit's first event in the lookback anchors a window of the metric's attribution length, every event inside `[anchor, anchor + window)` sums into that window's value, the first event at or after the window's end anchors the next window, and so on; each window sum is one value in the percentile population (each matches what one exposure-anchored analysis window would see, and repeat donors contribute several comparable samples), the percentile is taken over those per-unit values (GrowthBook caps unit-level values, not events), and the result is frozen into the phase (`metricDefinitions[].resolvedCap`, with `historyUnits`); fewer than 20 history units → no cap and a notice in the admin."
4
+ * The caller supplies only lookback events. `units` counts distinct units, not windows.
5
+ * Units without events never contribute: this conservative population is close to
6
+ * GrowthBook's ignore-zeros behavior and avoids a cap of 0 at low conversion.
7
+ */
8
+ export declare function perUnitHistory(events: readonly MetricEvent[], { windowHours }: {
9
+ windowHours: number;
10
+ }): {
11
+ values: number[];
12
+ units: number;
13
+ };
@@ -0,0 +1,39 @@
1
+ import { timestampMs, windowMs } from './aggregate.js';
2
+ /**
3
+ * Spec §5.4: "Default for revenue sources is `percentile 0.99`; the threshold is resolved at Start from the source's own `query({ from: start − 90 d, to: start })` (no extra host contract): events are aggregated **per unit** in **repeated greedy windows**: the unit's first event in the lookback anchors a window of the metric's attribution length, every event inside `[anchor, anchor + window)` sums into that window's value, the first event at or after the window's end anchors the next window, and so on; each window sum is one value in the percentile population (each matches what one exposure-anchored analysis window would see, and repeat donors contribute several comparable samples), the percentile is taken over those per-unit values (GrowthBook caps unit-level values, not events), and the result is frozen into the phase (`metricDefinitions[].resolvedCap`, with `historyUnits`); fewer than 20 history units → no cap and a notice in the admin."
4
+ * The caller supplies only lookback events. `units` counts distinct units, not windows.
5
+ * Units without events never contribute: this conservative population is close to
6
+ * GrowthBook's ignore-zeros behavior and avoids a cap of 0 at low conversion.
7
+ */ export function perUnitHistory(events, { windowHours }) {
8
+ const duration = windowMs(windowHours);
9
+ const byUnit = new Map();
10
+ for (const event of events){
11
+ const row = {
12
+ ts: timestampMs(event.ts),
13
+ value: event.value
14
+ };
15
+ const rows = byUnit.get(event.unitId);
16
+ if (rows) rows.push(row);
17
+ else byUnit.set(event.unitId, [
18
+ row
19
+ ]);
20
+ }
21
+ const values = [];
22
+ for (const rows of byUnit.values()){
23
+ rows.sort((a, b)=>a.ts - b.ts);
24
+ let anchor;
25
+ let sum = 0;
26
+ for (const row of rows){
27
+ if (anchor === undefined || row.ts >= anchor + duration) {
28
+ if (anchor !== undefined) values.push(sum);
29
+ anchor = row.ts;
30
+ sum = row.value;
31
+ } else sum += row.value;
32
+ }
33
+ values.push(sum);
34
+ }
35
+ return {
36
+ values,
37
+ units: byUnit.size
38
+ };
39
+ }
@@ -0,0 +1,16 @@
1
+ import type { ExposedUnit, MetricEvent, Timestamp } from './aggregate.js';
2
+ /**
3
+ * Spec §8.3: credit once to the earliest exposed linked visitor; conflicts are diagnostic only.
4
+ * Callers must pass user ids through String() for both identities and user-keyed source events.
5
+ */
6
+ export declare function creditUserEvents(units: ReadonlyMap<string, ExposedUnit>, identities: readonly {
7
+ anonymousId: string;
8
+ userId: string;
9
+ }[], userEvents: readonly {
10
+ userId: string;
11
+ value: number;
12
+ ts: Timestamp;
13
+ }[]): {
14
+ events: MetricEvent[];
15
+ conflicts: number;
16
+ };
@@ -0,0 +1,46 @@
1
+ import { timestampMs } from './aggregate.js';
2
+ /**
3
+ * Spec §8.3: credit once to the earliest exposed linked visitor; conflicts are diagnostic only.
4
+ * Callers must pass user ids through String() for both identities and user-keyed source events.
5
+ */ export function creditUserEvents(units, identities, userEvents) {
6
+ const users = new Map();
7
+ const starts = new Map();
8
+ for (const [unitId, unit] of units)starts.set(unitId, timestampMs(unit.firstTs));
9
+ for (const { anonymousId, userId } of identities){
10
+ const unit = units.get(anonymousId);
11
+ if (!unit) continue;
12
+ const firstTs = starts.get(anonymousId);
13
+ const user = users.get(userId);
14
+ if (!user) users.set(userId, {
15
+ unitId: anonymousId,
16
+ firstTs,
17
+ arms: new Set([
18
+ unit.arm
19
+ ])
20
+ });
21
+ else {
22
+ user.arms.add(unit.arm);
23
+ // Stable tie-break for simultaneous exposures, independent of identity row order.
24
+ if (firstTs < user.firstTs || firstTs === user.firstTs && anonymousId < user.unitId) {
25
+ user.unitId = anonymousId;
26
+ user.firstTs = firstTs;
27
+ }
28
+ }
29
+ }
30
+ let conflicts = 0;
31
+ for (const user of users.values())if (user.arms.size > 1) conflicts++;
32
+ const events = [];
33
+ for (const event of userEvents){
34
+ timestampMs(event.ts);
35
+ const user = users.get(event.userId);
36
+ if (user) events.push({
37
+ unitId: user.unitId,
38
+ value: event.value,
39
+ ts: event.ts
40
+ });
41
+ }
42
+ return {
43
+ events,
44
+ conflicts
45
+ };
46
+ }
@@ -0,0 +1,15 @@
1
+ import type { GaussianPrior, TestStatistic } from '@delmaredigital/experiments-stats';
2
+ import type { Snapshot } from '../types/snapshot.js';
3
+ /** Present the stats engine's pairwise sample estimate at observed exposed-unit
4
+ * traffic. v1 planning uses 10% relative lift and 80% power (as Review does).
5
+ * Called for running test verdict rows 4 and 9; rollout and concluded tests omit estimates.
6
+ */
7
+ export declare function remainingPower(input: {
8
+ baseline: TestStatistic;
9
+ variants: readonly TestStatistic[];
10
+ elapsedDays: number;
11
+ alpha: number;
12
+ method: Snapshot['method'];
13
+ sequentialTuningParameter: number;
14
+ prior: GaussianPrior;
15
+ }): Snapshot['power'];
@@ -0,0 +1,60 @@
1
+ import { abTestMoments, midExperimentPower, sequentialTwoSidedTTest } from '@delmaredigital/experiments-stats';
2
+ /** Present the stats engine's pairwise sample estimate at observed exposed-unit
3
+ * traffic. v1 planning uses 10% relative lift and 80% power (as Review does).
4
+ * Called for running test verdict rows 4 and 9; rollout and concluded tests omit estimates.
5
+ */ export function remainingPower(input) {
6
+ const lowerBound = input.method === 'sequential';
7
+ const unknown = {
8
+ daysRemaining: null,
9
+ exposuresNeededPerArm: null,
10
+ lowerBound,
11
+ feasible: true
12
+ };
13
+ if (input.elapsedDays <= 0 || input.variants.length === 0) return unknown;
14
+ const estimates = input.variants.map((variant)=>{
15
+ const moments = abTestMoments(input.baseline, variant);
16
+ const result = sequentialTwoSidedTTest(input.baseline, variant, {
17
+ alpha: input.alpha,
18
+ sequentialTuningParameter: input.sequentialTuningParameter
19
+ });
20
+ if (moments.errorMessage) return null;
21
+ try {
22
+ return {
23
+ result: midExperimentPower(moments, result, {
24
+ alpha: input.alpha
25
+ }, {
26
+ targetMde: 0.1,
27
+ targetPower: 0.8,
28
+ sequential: lowerBound,
29
+ sequentialTuningParameter: input.sequentialTuningParameter,
30
+ priorEffect: input.method === 'bayesian' ? input.prior : null
31
+ }),
32
+ n: moments.pairwiseSampleSize
33
+ };
34
+ } catch {
35
+ return null;
36
+ }
37
+ });
38
+ if (estimates.some((estimate)=>estimate === null)) return unknown;
39
+ if (estimates.some((estimate)=>estimate.result.updateMessage === 'unsuccessful')) return {
40
+ ...unknown,
41
+ feasible: false
42
+ };
43
+ let daysRemaining = 0;
44
+ let exposuresNeededPerArm = 0;
45
+ for (const estimate of estimates){
46
+ const { result, n } = estimate;
47
+ const additional = result.scalingFactor === 0 ? 0 : result.additionalUsers;
48
+ if (additional === null || !Number.isFinite(additional)) return unknown;
49
+ daysRemaining = Math.max(daysRemaining, Math.ceil(additional / (n / input.elapsedDays)));
50
+ // Upstream additionalUsers is pairwise, so the UI's per-arm total is half
51
+ // the projected pair. Day estimates preserve the actual observed arm ratio.
52
+ exposuresNeededPerArm = Math.max(exposuresNeededPerArm, Math.ceil((n + additional) / 2));
53
+ }
54
+ return {
55
+ daysRemaining,
56
+ exposuresNeededPerArm,
57
+ lowerBound,
58
+ feasible: daysRemaining <= 90
59
+ };
60
+ }