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

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 +590 -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 +107 -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 +141 -0
  453. package/dist/types/contracts.js +1 -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 +138 -3
@@ -0,0 +1,81 @@
1
+ import { XpRefusal } from '../errors.js';
2
+ export async function readPhaseRecord(req, experimentId, phase) {
3
+ // §7.8: frozen configuration lookup serves an authorised transition.
4
+ const { docs } = await req.payload.find({
5
+ collection: 'xp-phases',
6
+ req,
7
+ overrideAccess: true,
8
+ depth: 0,
9
+ limit: 1,
10
+ where: {
11
+ and: [
12
+ {
13
+ experiment: {
14
+ equals: experimentId
15
+ }
16
+ },
17
+ {
18
+ phase: {
19
+ equals: phase
20
+ }
21
+ }
22
+ ]
23
+ }
24
+ });
25
+ if (!docs[0]) throw new XpRefusal('not-found', 'The phase record was not found.', {
26
+ experimentId,
27
+ phase
28
+ }, 404);
29
+ return docs[0];
30
+ }
31
+ /** Caller holds the experiment lock and passes the kernel's transaction req. */ export async function writePhaseRecord(req, record) {
32
+ // §7.8: transition endpoints' writes to plugin collections.
33
+ return req.payload.create({
34
+ collection: 'xp-phases',
35
+ data: record,
36
+ req,
37
+ overrideAccess: true
38
+ });
39
+ }
40
+ /** End only an open phase, preserving the original stop time on a later decide.
41
+ * Caller holds the experiment lock and passes the kernel's transaction req.
42
+ */ export async function setPhaseEndedAt(req, experimentId, phase, at) {
43
+ // §7.8: phase lookup for transition endpoints' writes to plugin collections.
44
+ const { docs } = await req.payload.find({
45
+ collection: 'xp-phases',
46
+ req,
47
+ limit: 1,
48
+ depth: 0,
49
+ where: {
50
+ and: [
51
+ {
52
+ experiment: {
53
+ equals: experimentId
54
+ }
55
+ },
56
+ {
57
+ phase: {
58
+ equals: phase
59
+ }
60
+ }
61
+ ]
62
+ },
63
+ overrideAccess: true
64
+ });
65
+ const record = docs[0];
66
+ if (!record) throw new XpRefusal('not-found', 'The phase record was not found.', {
67
+ experimentId,
68
+ phase
69
+ }, 404);
70
+ if (record.endedAt) return record;
71
+ // §7.8: transition endpoints' writes to plugin collections.
72
+ return req.payload.update({
73
+ collection: 'xp-phases',
74
+ req,
75
+ id: record.id,
76
+ data: {
77
+ endedAt: at
78
+ },
79
+ overrideAccess: true
80
+ });
81
+ }
@@ -0,0 +1,26 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { Arm, DocId, FlagPoint, ResolvedOptions } from '../types/contracts.js';
3
+ import type { ArmSlot } from '../types/puck.js';
4
+ export interface BlockPlacement {
5
+ collection: string;
6
+ id: DocId;
7
+ title: string;
8
+ published: boolean;
9
+ slots: Record<ArmSlot, number>;
10
+ locked?: boolean;
11
+ }
12
+ export type FlagPlacement = Awaited<ReturnType<NonNullable<FlagPoint['placements']>>>[number];
13
+ /** Each matching ExperimentSlot is checked separately, including multiple slots
14
+ * in one document: content elsewhere in the document cannot hide an empty slot.
15
+ */
16
+ export declare function scanBlockPlacements(req: PayloadRequest, { slug, puckCollections }: {
17
+ slug: string;
18
+ puckCollections: NonNullable<ResolvedOptions['puck']>['collections'];
19
+ }): Promise<BlockPlacement[]>;
20
+ export declare function flagPlacements<TUser extends {
21
+ id: DocId;
22
+ }>(options: ResolvedOptions<TUser>, experiment: {
23
+ id: DocId;
24
+ flagPoint?: string | null;
25
+ arms: Arm[];
26
+ }, req: PayloadRequest): Promise<FlagPlacement[]>;
@@ -0,0 +1,89 @@
1
+ import { puckCollections as normalisePuckCollections } from '../options.js';
2
+ const slots = [
3
+ 'control',
4
+ 'variantA',
5
+ 'variantB',
6
+ 'variantC'
7
+ ];
8
+ /** Each matching ExperimentSlot is checked separately, including multiple slots
9
+ * in one document: content elsewhere in the document cannot hide an empty slot.
10
+ */ export async function scanBlockPlacements(req, { slug, puckCollections }) {
11
+ const placements = [];
12
+ for (const { slug: collection, field } of normalisePuckCollections({
13
+ puck: {
14
+ enabled: true,
15
+ collections: puckCollections
16
+ }
17
+ })){
18
+ const publishedIds = new Set();
19
+ const draftsEnabled = req.payload.config.collections?.some((config)=>config.slug === collection && config.versions && config.versions.drafts);
20
+ for (const draft of [
21
+ false,
22
+ true
23
+ ]){
24
+ let page = 1;
25
+ while(true){
26
+ const result = await req.payload.find({
27
+ collection,
28
+ req,
29
+ overrideAccess: false,
30
+ limit: 100,
31
+ page,
32
+ depth: 0,
33
+ draft,
34
+ includeLockStatus: true,
35
+ ...!draft && draftsEnabled ? {
36
+ where: {
37
+ _status: {
38
+ equals: 'published'
39
+ }
40
+ }
41
+ } : {}
42
+ });
43
+ for (const doc of result.docs){
44
+ if (!draft && doc._status === 'draft') continue;
45
+ if (draft && doc._status !== 'draft' && publishedIds.has(String(doc.id))) continue;
46
+ if (!draft) publishedIds.add(String(doc.id));
47
+ function walk(value) {
48
+ if (!value || typeof value !== 'object') return;
49
+ if (Array.isArray(value)) {
50
+ value.forEach(walk);
51
+ return;
52
+ }
53
+ const node = value;
54
+ const props = node.props;
55
+ const ref = props?.experiment;
56
+ const experiment = typeof ref === 'string' ? ref : ref && typeof ref === 'object' && 'slug' in ref ? ref.slug : null;
57
+ if (node.type === 'ExperimentSlot' && props && experiment === slug) {
58
+ placements.push({
59
+ collection,
60
+ id: doc.id,
61
+ title: String(doc.title ?? doc.name ?? doc.id),
62
+ published: !draft || doc._status === 'published',
63
+ slots: Object.fromEntries(slots.map((slot)=>[
64
+ slot,
65
+ Array.isArray(props[slot]) ? props[slot].length : 0
66
+ ])),
67
+ locked: doc._isLocked === true || doc.locked === true
68
+ });
69
+ }
70
+ Object.values(node).forEach(walk);
71
+ }
72
+ walk(doc[field]);
73
+ }
74
+ if (!result.hasNextPage) break;
75
+ page++;
76
+ }
77
+ }
78
+ }
79
+ return placements;
80
+ }
81
+ export async function flagPlacements(options, experiment, req) {
82
+ const point = options.flagPoints?.find((point)=>point.id === experiment.flagPoint);
83
+ if (!point?.placements) return [];
84
+ return point.placements({
85
+ experimentId: experiment.id,
86
+ arms: experiment.arms.filter((arm)=>arm.kind !== 'holdout').map((arm)=>arm.key),
87
+ req
88
+ });
89
+ }
@@ -0,0 +1,21 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { PowerWeeksInput, PowerWeeksResult } from '@delmaredigital/experiments-stats';
3
+ import type { DocId, ResolvedOptions } from '../types/contracts.js';
4
+ import type { PreflightResult } from '../types/endpoints.js';
5
+ import type { CheckStartInput } from './checkStart.js';
6
+ export interface PreflightDependencies {
7
+ now?: Date;
8
+ estimate?: (input: PowerWeeksInput) => PowerWeeksResult | Promise<PowerWeeksResult>;
9
+ }
10
+ /** Read-only inputs shared with Start's pre-transaction checks. Start must re-read
11
+ * the document and lock-sensitive facts inside its transition (CORE-10b).
12
+ */
13
+ export declare function loadStartInputs<TUser extends {
14
+ id: DocId;
15
+ }>(req: PayloadRequest, options: ResolvedOptions<TUser>, experimentId: DocId): Promise<CheckStartInput | null>;
16
+ export declare function preflightData<TUser extends {
17
+ id: DocId;
18
+ }>(req: PayloadRequest, { experimentId, options }: {
19
+ experimentId: DocId;
20
+ options: ResolvedOptions<TUser>;
21
+ }, { now, estimate }?: PreflightDependencies): Promise<PreflightResult | null>;
@@ -0,0 +1,317 @@
1
+ import { powerMetricWeeks } from '@delmaredigital/experiments-stats';
2
+ import { checkStart, relationshipId } from './checkStart.js';
3
+ import { flagPlacements, scanBlockPlacements } from './placements.js';
4
+ const failureDetail = (cause)=>cause instanceof Error ? cause.message : 'Placement lookup failed.';
5
+ /** Read-only inputs shared with Start's pre-transaction checks. Start must re-read
6
+ * the document and lock-sensitive facts inside its transition (CORE-10b).
7
+ */ export async function loadStartInputs(req, options, experimentId) {
8
+ const found = await req.payload.find({
9
+ collection: 'xp-experiments',
10
+ req,
11
+ overrideAccess: false,
12
+ depth: 0,
13
+ limit: 1,
14
+ where: {
15
+ id: {
16
+ equals: experimentId
17
+ }
18
+ }
19
+ });
20
+ if (!found.docs[0]) return null;
21
+ const experiment = found.docs[0];
22
+ const ids = [
23
+ experiment.metrics?.primary,
24
+ ...experiment.metrics?.secondary ?? [],
25
+ ...experiment.metrics?.guardrails ?? []
26
+ ].map(relationshipId).filter((id)=>id !== undefined);
27
+ const metrics = [];
28
+ if (ids.length) {
29
+ let page = 1;
30
+ while(true){
31
+ const result = await req.payload.find({
32
+ collection: 'xp-metrics',
33
+ req,
34
+ overrideAccess: false,
35
+ depth: 0,
36
+ limit: 100,
37
+ page,
38
+ where: {
39
+ id: {
40
+ in: ids
41
+ }
42
+ }
43
+ });
44
+ metrics.push(...result.docs);
45
+ if (!result.hasNextPage) break;
46
+ page++;
47
+ }
48
+ }
49
+ const input = {
50
+ experiment,
51
+ metrics,
52
+ metricSources: options.metricSources,
53
+ flagPoints: options.flagPoints,
54
+ attributeKeys: options.attributeKeys
55
+ };
56
+ if (experiment.kind === 'block') {
57
+ try {
58
+ input.placements = await scanBlockPlacements(req, {
59
+ slug: experiment.slug,
60
+ puckCollections: options.puck?.collections ?? []
61
+ });
62
+ } catch (cause) {
63
+ input.placementError = failureDetail(cause);
64
+ }
65
+ }
66
+ if (experiment.kind === 'flag') {
67
+ const point = options.flagPoints?.find((point)=>point.id === experiment.flagPoint);
68
+ if (point) {
69
+ // Match the save hook: the control always uses effective point defaults.
70
+ let params = point.defaultParams;
71
+ try {
72
+ const state = await req.payload.findGlobal({
73
+ slug: 'xp-state',
74
+ req,
75
+ overrideAccess: false,
76
+ depth: 0
77
+ });
78
+ const defaults = state.flagDefaults;
79
+ params = defaults?.[point.id]?.params ?? params;
80
+ } catch (cause) {
81
+ const error = cause;
82
+ if (error?.status !== 403 && error?.name !== 'Forbidden') throw cause;
83
+ }
84
+ experiment.arms = experiment.arms.map((arm)=>arm.kind === 'control' ? {
85
+ ...arm,
86
+ params
87
+ } : arm);
88
+ }
89
+ if (point?.placements) {
90
+ try {
91
+ const placements = await flagPlacements(options, experiment, req);
92
+ if (Array.isArray(placements) && placements.every((p)=>p && typeof p === 'object' && typeof p.arm === 'string')) input.flagPlacements = placements;
93
+ else input.flagPlacementError = 'Placement lookup returned an invalid result.';
94
+ } catch (cause) {
95
+ input.flagPlacementError = failureDetail(cause);
96
+ }
97
+ }
98
+ if (experiment.flagPoint) {
99
+ // At most one row can be this experiment; two rows suffice to find a peer.
100
+ const busy = await req.payload.find({
101
+ collection: 'xp-experiments',
102
+ req,
103
+ overrideAccess: false,
104
+ depth: 0,
105
+ limit: 2,
106
+ where: {
107
+ and: [
108
+ {
109
+ flagPoint: {
110
+ equals: experiment.flagPoint
111
+ }
112
+ },
113
+ {
114
+ status: {
115
+ in: [
116
+ 'running',
117
+ 'decided'
118
+ ]
119
+ }
120
+ }
121
+ ]
122
+ }
123
+ });
124
+ input.flagPointBusy = busy.docs.some((doc)=>String(doc.id) !== String(experiment.id));
125
+ }
126
+ }
127
+ const layerId = relationshipId(experiment.layer);
128
+ if (layerId !== undefined) {
129
+ const layers = await req.payload.find({
130
+ collection: 'xp-layers',
131
+ req,
132
+ overrideAccess: false,
133
+ depth: 0,
134
+ limit: 1,
135
+ where: {
136
+ id: {
137
+ equals: layerId
138
+ }
139
+ }
140
+ });
141
+ const layer = layers.docs[0];
142
+ const usedSlices = [];
143
+ let page = 1;
144
+ while(true){
145
+ const result = await req.payload.find({
146
+ collection: 'xp-experiments',
147
+ req,
148
+ overrideAccess: false,
149
+ depth: 0,
150
+ limit: 100,
151
+ page,
152
+ where: {
153
+ and: [
154
+ {
155
+ layer: {
156
+ equals: layerId
157
+ }
158
+ },
159
+ {
160
+ status: {
161
+ in: [
162
+ 'running',
163
+ 'decided'
164
+ ]
165
+ }
166
+ }
167
+ ]
168
+ }
169
+ });
170
+ for (const doc of result.docs)if (Array.isArray(doc.layerSlices)) usedSlices.push(...doc.layerSlices);
171
+ if (!result.hasNextPage) break;
172
+ page++;
173
+ }
174
+ input.layer = layer ? {
175
+ unitType: layer.unitType,
176
+ usedSlices
177
+ } : null;
178
+ }
179
+ return input;
180
+ }
181
+ async function primaryHistory(source, now) {
182
+ if (!source) return null;
183
+ const from = new Date(now.getTime() - 28 * 86400000);
184
+ try {
185
+ const rows = await source.query({
186
+ from,
187
+ to: now
188
+ });
189
+ const units = new Map();
190
+ const dailyUnits = new Set();
191
+ for (const row of rows){
192
+ if (!Number.isFinite(row.value) || !Number.isFinite(new Date(row.ts).getTime())) return null;
193
+ if (new Date(row.ts) < from || new Date(row.ts) >= now) continue;
194
+ units.set(row.unitId, (units.get(row.unitId) ?? 0) + row.value);
195
+ dailyUnits.add(JSON.stringify([
196
+ new Date(row.ts).toISOString().slice(0, 10),
197
+ row.unitId
198
+ ]));
199
+ }
200
+ return {
201
+ values: [
202
+ ...units.values()
203
+ ],
204
+ dailyConverters: dailyUnits.size
205
+ };
206
+ } catch {
207
+ return null;
208
+ }
209
+ }
210
+ export async function preflightData(req, { experimentId, options }, { now = new Date(), estimate = powerMetricWeeks } = {}) {
211
+ const input = await loadStartInputs(req, options, experimentId);
212
+ if (!input) return null;
213
+ const { experiment: e } = input;
214
+ const primaryId = relationshipId(e.metrics?.primary);
215
+ const metric = input.metrics.find((metric)=>String(metric.id) === String(primaryId));
216
+ const source = options.metricSources?.find((source)=>source.id === metric?.source);
217
+ const historical = await primaryHistory(source, now);
218
+ const history = historical?.values ?? null;
219
+ const reach = e.analysis?.reachPerDay;
220
+ const trafficPerDay = typeof reach === 'number' && Number.isFinite(reach) && reach > 0 ? reach : null;
221
+ const value = history === null ? null : source?.kind === 'binomial' ? trafficPerDay === null ? null : historical.dailyConverters / 28 / trafficPerDay : history.length ? history.reduce((sum, value)=>sum + value, 0) / history.length : 0;
222
+ // A rate of 1 or more means reachPerDay is below the converters; no honest baseline then.
223
+ const baseline = primaryId === undefined || value === null || source?.kind === 'binomial' && value >= 1 ? null : {
224
+ metricId: primaryId,
225
+ value,
226
+ days: 28
227
+ };
228
+ let power = null;
229
+ if (trafficPerDay !== null && baseline && source && history) {
230
+ const compared = e.arms.filter((arm)=>arm.kind !== 'holdout');
231
+ const control = compared.find((arm)=>arm.kind === 'control');
232
+ const variant = compared.find((arm)=>arm.kind === 'variant');
233
+ const savedDuration = e.analysis?.plannedDurationDays && e.analysis.plannedDurationDays > 0 ? e.analysis.plannedDurationDays : undefined;
234
+ const planned = savedDuration ?? 14;
235
+ const enrolled = trafficPerDay * e.allocation / 100;
236
+ const pairShare = ((control?.weight ?? 0) + (variant?.weight ?? 0)) / 100;
237
+ const tuning = enrolled * planned * pairShare;
238
+ // Power's equal-arm model uses the smallest compared arm, so unequal weights
239
+ // and holdout traffic never overstate the information in a comparison.
240
+ const usersPerWeek = enrolled * 7 * Math.min(...compared.map((arm)=>arm.weight / 100)) * compared.length;
241
+ const n = Math.max(trafficPerDay * 28, history.length);
242
+ const mean = history.reduce((sum, value)=>sum + value, 0) / n;
243
+ const variance = history.reduce((sum, value)=>sum + value * value, 0) / n - mean * mean;
244
+ const conversionRate = baseline.value;
245
+ // v1 planning assumption: 10% relative lift at 80% power, alpha fixed at 5%.
246
+ const params = source.kind === 'binomial' ? {
247
+ type: 'binomial',
248
+ conversionRate,
249
+ effectSize: 0.1
250
+ } : {
251
+ type: 'mean',
252
+ mean,
253
+ standardDeviation: Math.sqrt(Math.max(0, variance)),
254
+ effectSize: 0.1
255
+ };
256
+ if (control && variant && usersPerWeek > 0 && tuning > 0 && (source.kind !== 'binomial' || conversionRate > 0 && conversionRate < 1) && (source.kind === 'binomial' || mean > 0)) {
257
+ try {
258
+ const alpha = e.analysis?.method === 'bayesian' ? 0.05 : 0.05 / Math.max(1, compared.filter((arm)=>arm.kind === 'variant').length);
259
+ const estimateAt = (sequentialTuningParameter)=>estimate({
260
+ metrics: {
261
+ primary: params
262
+ },
263
+ nVariations: compared.length,
264
+ nWeeks: 13,
265
+ alpha,
266
+ usersPerWeek,
267
+ targetPower: 0.8,
268
+ method: e.analysis?.method === 'bayesian' ? {
269
+ type: 'bayesian'
270
+ } : {
271
+ type: 'frequentist',
272
+ sequentialTesting: sequentialTuningParameter
273
+ }
274
+ });
275
+ let result = await estimateAt(tuning);
276
+ let weeks = result.sampleSizeAndRuntime.primary?.weeks ?? result.weekThreshold ?? 13;
277
+ const plannedDurationDays = savedDuration ?? Math.ceil(weeks * 7);
278
+ const sequentialTuningParameter = enrolled * plannedDurationDays * pairShare;
279
+ if (savedDuration === undefined) {
280
+ // Re-evaluate using exactly the duration and tuning Review will prefill.
281
+ result = await estimateAt(sequentialTuningParameter);
282
+ weeks = result.sampleSizeAndRuntime.primary?.weeks ?? result.weekThreshold ?? 13;
283
+ }
284
+ power = {
285
+ weeks,
286
+ plannedDurationDays,
287
+ sequentialTuningParameter,
288
+ feasible: Number.isFinite(weeks) && weeks * 7 <= 90 && result.sampleSizeAndRuntime.primary !== undefined
289
+ };
290
+ } catch {}
291
+ }
292
+ }
293
+ const requiredSlots = e.arms.filter((arm)=>arm.kind !== 'holdout').map((arm)=>arm.slot);
294
+ const placements = (input.placements ?? []).map((placement)=>({
295
+ collection: placement.collection,
296
+ id: placement.id,
297
+ title: placement.title,
298
+ ok: requiredSlots.every((slot)=>slot && placement.slots[slot] > 0),
299
+ detail: placement.published ? 'Published' : 'Draft'
300
+ }));
301
+ for (const placement of input.flagPlacements ?? [])placements.push({
302
+ collection: e.flagPoint ?? '',
303
+ id: placement.arm,
304
+ title: placement.arm,
305
+ ok: placement.ok,
306
+ ...placement.detail ? {
307
+ detail: placement.detail
308
+ } : {}
309
+ });
310
+ return {
311
+ checks: checkStart(input),
312
+ placements,
313
+ baseline,
314
+ trafficPerDay,
315
+ estimate: power
316
+ };
317
+ }
@@ -0,0 +1,8 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { DocId } from '../types/contracts.js';
3
+ export declare function releaseHoldout(req: PayloadRequest, { experimentId }: {
4
+ experimentId: DocId;
5
+ }): Promise<{
6
+ doc: import("payload").JsonObject & import("payload").TypeWithID;
7
+ revision: number;
8
+ }>;
@@ -0,0 +1,30 @@
1
+ import { relationshipId } from './checkStart.js';
2
+ import { withTransition } from './kernel.js';
3
+ import { readPhaseRecord, setPhaseEndedAt } from './phaseRecord.js';
4
+ import { readExperiment, refuse, updateExperiment } from './start.js';
5
+ export async function releaseHoldout(req, { experimentId }) {
6
+ const before = await readExperiment(req, experimentId);
7
+ const { result: doc, revision } = await withTransition(req, {
8
+ experimentId,
9
+ layerId: relationshipId(before.layer)
10
+ }, async ({ req })=>{
11
+ const current = await readExperiment(req, experimentId);
12
+ const decision = current.decision;
13
+ if (current.status !== 'decided' || !decision?.winnerArm || decision.holdoutReleasedAt) refuse('invalid-status');
14
+ const phase = await readPhaseRecord(req, experimentId, current.phase);
15
+ if (phase.phaseKind !== 'rollout' || phase.endedAt) refuse('invalid-status');
16
+ const at = new Date().toISOString();
17
+ await setPhaseEndedAt(req, experimentId, phase.phase, at);
18
+ return updateExperiment(req, experimentId, {
19
+ layerSlices: [],
20
+ decision: {
21
+ ...decision,
22
+ holdoutReleasedAt: at
23
+ }
24
+ });
25
+ });
26
+ return {
27
+ doc,
28
+ revision: revision
29
+ };
30
+ }
@@ -0,0 +1,28 @@
1
+ import type { DocId, MetricSource } from '../types/contracts.js';
2
+ export interface MetricDefinition extends Record<string, unknown> {
3
+ id: DocId;
4
+ source: string;
5
+ attributionWindowHours?: number | null;
6
+ minSampleSize?: number | null;
7
+ guardrailThreshold?: number | null;
8
+ capping?: {
9
+ type?: 'percentile' | 'absolute' | null;
10
+ value?: number | null;
11
+ } | null;
12
+ }
13
+ /** Resolve revenue caps before taking transition locks. Host history failures
14
+ * disable capping for that metric and travel back as an explanatory detail.
15
+ */
16
+ export declare function resolveCaps(metrics: MetricDefinition[], sources: MetricSource[], start: Date): Promise<{
17
+ definitions: (MetricDefinition & {
18
+ kind?: MetricSource["kind"];
19
+ keyedBy?: MetricSource["keyedBy"];
20
+ resolvedCap: number | null;
21
+ historyUnits?: number;
22
+ capNotice?: "no-history";
23
+ })[];
24
+ detail: {
25
+ metricId: DocId;
26
+ message: string;
27
+ }[];
28
+ }>;
@@ -0,0 +1,68 @@
1
+ import { resolvePercentileCap } from '@delmaredigital/experiments-stats';
2
+ import { perUnitHistory } from '../results/history.js';
3
+ /** Resolve revenue caps before taking transition locks. Host history failures
4
+ * disable capping for that metric and travel back as an explanatory detail.
5
+ */ export async function resolveCaps(metrics, sources, start) {
6
+ const detail = [];
7
+ const definitions = [];
8
+ const from = new Date(start.getTime() - 90 * 86400000);
9
+ for (const metric of metrics){
10
+ const source = sources.find((source)=>source.id === metric.source);
11
+ const kind = metric.source.startsWith('events:') ? 'binomial' : source?.kind;
12
+ const definition = {
13
+ ...structuredClone(metric),
14
+ ...source ? {
15
+ keyedBy: source.keyedBy
16
+ } : {},
17
+ kind,
18
+ resolvedCap: null,
19
+ historyUnits: undefined,
20
+ capNotice: undefined
21
+ };
22
+ const cap = metric.capping?.type ? metric.capping : kind === 'revenue' ? {
23
+ type: 'percentile',
24
+ value: 0.99
25
+ } : null;
26
+ if (kind === 'revenue' || !source && cap && !metric.source.startsWith('events:')) {
27
+ definition.capping = cap;
28
+ if (cap?.type === 'absolute' && typeof cap.value === 'number' && Number.isFinite(cap.value) && cap.value >= 0) {
29
+ definition.resolvedCap = cap.value;
30
+ } else if (cap?.type === 'percentile') {
31
+ definition.historyUnits = 0;
32
+ try {
33
+ if (!source) throw new Error('Metric source unavailable');
34
+ const rows = (await source.query({
35
+ from,
36
+ to: start
37
+ })).filter((row)=>new Date(row.ts) >= from && new Date(row.ts) < start);
38
+ if (rows.some((row)=>!Number.isFinite(row.value))) throw new Error('Invalid metric history');
39
+ const history = perUnitHistory(rows, {
40
+ windowHours: metric.attributionWindowHours ?? 168
41
+ });
42
+ definition.historyUnits = history.units;
43
+ if (history.units >= 20) {
44
+ // The collection permits 1 (the maximum); the stats percentile API requires p < 1.
45
+ definition.resolvedCap = cap.value === 1 ? Math.max(...history.values) : resolvePercentileCap(history.values, cap.value ?? 0.99);
46
+ } else {
47
+ definition.capNotice = 'no-history';
48
+ detail.push({
49
+ metricId: metric.id,
50
+ message: 'Fewer than 20 history units; no cap was applied.'
51
+ });
52
+ }
53
+ } catch {
54
+ definition.capNotice = 'no-history';
55
+ detail.push({
56
+ metricId: metric.id,
57
+ message: 'Metric history is unavailable; no cap was applied.'
58
+ });
59
+ }
60
+ }
61
+ }
62
+ definitions.push(definition);
63
+ }
64
+ return {
65
+ definitions,
66
+ detail
67
+ };
68
+ }