@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,126 @@
1
+ import { readState } from '../db/state.js';
2
+ import { XpRefusal } from '../errors.js';
3
+ import { relationshipId } from './checkStart.js';
4
+ import { withTransition } from './kernel.js';
5
+ import { readExperiment, refuse, updateExperiment } from './start.js';
6
+ import { resolveCaps } from './resolveCaps.js';
7
+ export async function addMetric(req, { experimentId, metric, role, options }) {
8
+ if (role !== 'secondary' && role !== 'guardrail') refuse('invalid-body');
9
+ const before = await readExperiment(req, experimentId);
10
+ if (before.status !== 'running') refuse('invalid-status');
11
+ const found = await req.payload.find({
12
+ collection: 'xp-metrics',
13
+ req,
14
+ overrideAccess: false,
15
+ depth: 0,
16
+ limit: 1,
17
+ where: {
18
+ id: {
19
+ equals: metric
20
+ }
21
+ }
22
+ });
23
+ if (!found.docs[0]) throw new XpRefusal('not-found', 'Metric not found', undefined, 404);
24
+ const metricId = found.docs[0].id;
25
+ const at = new Date();
26
+ // §5.4: cap history ends at the phase start, never inside the running phase.
27
+ const started = before.startedAt;
28
+ const startedAt = typeof started === 'string' ? new Date(started) : at;
29
+ const caps = await resolveCaps([
30
+ found.docs[0]
31
+ ], options.metricSources ?? [], startedAt);
32
+ // Metrics are analysis-only; the assignment manifest is unchanged, so no bump.
33
+ const { result } = await withTransition(req, {
34
+ experimentId,
35
+ bump: false
36
+ }, async ({ req, exec })=>{
37
+ const current = await readExperiment(req, experimentId);
38
+ if (current.status !== 'running') refuse('invalid-status');
39
+ const ids = [
40
+ current.metrics?.primary,
41
+ ...current.metrics?.secondary ?? [],
42
+ ...current.metrics?.guardrails ?? []
43
+ ].map(relationshipId);
44
+ if (ids.some((id)=>id !== undefined && String(id) === String(metric))) refuse('duplicate-metric', {
45
+ message: 'This metric is already part of the experiment.'
46
+ });
47
+ const definition = caps.definitions[0];
48
+ if (current.unitType === 'user' && definition.keyedBy === 'visitor' && !definition.source.startsWith('events:')) refuse('unit-source-mismatch');
49
+ // §7.8: phase lookup serves the access-authorised transition write.
50
+ const phases = await req.payload.find({
51
+ collection: 'xp-phases',
52
+ req,
53
+ overrideAccess: true,
54
+ depth: 0,
55
+ limit: 1,
56
+ where: {
57
+ and: [
58
+ {
59
+ experiment: {
60
+ equals: experimentId
61
+ }
62
+ },
63
+ {
64
+ phase: {
65
+ equals: current.phase
66
+ }
67
+ }
68
+ ]
69
+ }
70
+ });
71
+ const phase = phases.docs[0];
72
+ if (!phase) throw new XpRefusal('not-found', 'The phase record was not found.', undefined, 404);
73
+ // §7.8: append one frozen metric definition under the experiment lock.
74
+ const analysis = phase.analysis;
75
+ await req.payload.update({
76
+ collection: 'xp-phases',
77
+ req,
78
+ overrideAccess: true,
79
+ id: phase.id,
80
+ data: {
81
+ analysis: {
82
+ ...analysis,
83
+ minSampleSizes: {
84
+ ...analysis?.minSampleSizes,
85
+ [metricId]: definition.minSampleSize ?? 150
86
+ },
87
+ ...role === 'guardrail' ? {
88
+ guardrailThresholds: {
89
+ ...analysis?.guardrailThresholds,
90
+ [metricId]: definition.guardrailThreshold ?? 0.05
91
+ }
92
+ } : {}
93
+ },
94
+ metricDefinitions: [
95
+ ...phase.metricDefinitions ?? [],
96
+ {
97
+ ...definition,
98
+ metricId,
99
+ role,
100
+ addedAt: at.toISOString()
101
+ }
102
+ ]
103
+ }
104
+ });
105
+ const field = role === 'guardrail' ? 'guardrails' : 'secondary';
106
+ const doc = await updateExperiment(req, experimentId, {
107
+ metrics: {
108
+ ...current.metrics,
109
+ [field]: [
110
+ ...current.metrics?.[field] ?? [],
111
+ metricId
112
+ ]
113
+ }
114
+ });
115
+ return {
116
+ doc,
117
+ revision: (await readState(exec))?.revision ?? 0
118
+ };
119
+ });
120
+ return {
121
+ ...result,
122
+ ...caps.detail.length ? {
123
+ detail: caps.detail
124
+ } : {}
125
+ };
126
+ }
@@ -0,0 +1,9 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { DocId } from '../types/contracts.js';
3
+ export declare function allocate(req: PayloadRequest, { experimentId, allocation }: {
4
+ experimentId: DocId;
5
+ allocation: number;
6
+ }): Promise<{
7
+ doc: import("payload").JsonObject & import("payload").TypeWithID;
8
+ revision: number;
9
+ }>;
@@ -0,0 +1,84 @@
1
+ import { XpRefusal } from '../errors.js';
2
+ import { appendAllocationChange } from '../db/appends.js';
3
+ import { claimSlices } from '../db/slices.js';
4
+ import { relationshipId } from './checkStart.js';
5
+ import { withTransition } from './kernel.js';
6
+ import { readExperiment, refuse, updateExperiment } from './start.js';
7
+ export async function allocate(req, { experimentId, allocation }) {
8
+ if (!Number.isInteger(allocation) || allocation < 1 || allocation > 100) refuse('invalid-body');
9
+ const before = await readExperiment(req, experimentId);
10
+ const { result: doc, revision } = await withTransition(req, {
11
+ experimentId,
12
+ layerId: relationshipId(before.layer)
13
+ }, async ({ req, exec })=>{
14
+ const current = await readExperiment(req, experimentId);
15
+ if (current.status !== 'running') refuse('invalid-status');
16
+ if (allocation <= current.allocation) refuse('allocation-lower');
17
+ const layerId = relationshipId(current.layer);
18
+ const layerSlices = layerId === undefined ? [] : [
19
+ ...current.layerSlices ?? [],
20
+ ...await claimSlices(exec, {
21
+ layerId,
22
+ experimentId,
23
+ count: allocation - current.allocation
24
+ })
25
+ ].sort((a, b)=>a - b);
26
+ await appendAllocationChange(exec, {
27
+ experimentId,
28
+ phase: current.phase,
29
+ entry: {
30
+ at: new Date().toISOString(),
31
+ from: current.allocation,
32
+ to: allocation
33
+ }
34
+ });
35
+ // The compiler reads coverage from the phase, so keep its current allocation
36
+ // in sync while allocationChanges retains the full audit trail.
37
+ // §7.8: phase lookup for the transition write.
38
+ const phases = await req.payload.find({
39
+ collection: 'xp-phases',
40
+ req,
41
+ overrideAccess: true,
42
+ depth: 0,
43
+ limit: 1,
44
+ where: {
45
+ and: [
46
+ {
47
+ experiment: {
48
+ equals: experimentId
49
+ }
50
+ },
51
+ {
52
+ phase: {
53
+ equals: current.phase
54
+ }
55
+ }
56
+ ]
57
+ }
58
+ });
59
+ if (!phases.docs[0]) throw new XpRefusal('not-found', 'The phase record was not found.', undefined, 404);
60
+ // §7.8: transition writes the phase's widened coverage.
61
+ await req.payload.update({
62
+ collection: 'xp-phases',
63
+ req,
64
+ overrideAccess: true,
65
+ id: phases.docs[0].id,
66
+ data: {
67
+ allocation,
68
+ analysis: {
69
+ ...phases.docs[0].analysis,
70
+ layerSlices
71
+ }
72
+ }
73
+ });
74
+ // Preserve seed, arms and existing slices: only coverage widens.
75
+ return updateExperiment(req, experimentId, {
76
+ allocation,
77
+ layerSlices
78
+ });
79
+ });
80
+ return {
81
+ doc,
82
+ revision: revision
83
+ };
84
+ }
@@ -0,0 +1,9 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { DocId } from '../types/contracts.js';
3
+ import type { ArchiveBody } from '../types/endpoints.js';
4
+ export declare function archive(req: PayloadRequest, { experimentId, mode, confirmation }: ArchiveBody & {
5
+ experimentId: DocId;
6
+ }): Promise<{
7
+ doc: import("payload").JsonObject & import("payload").TypeWithID;
8
+ revision: number;
9
+ }>;
@@ -0,0 +1,46 @@
1
+ import { relationshipId } from './checkStart.js';
2
+ import { expectedConfirm, matchesConfirm, storedWinnerLabel } from './confirmPhrases.js';
3
+ import { withTransition } from './kernel.js';
4
+ import { setPhaseEndedAt } from './phaseRecord.js';
5
+ import { readExperiment, refuse, updateExperiment } from './start.js';
6
+ export async function archive(req, { experimentId, mode, confirmation }) {
7
+ const before = await readExperiment(req, experimentId);
8
+ const { result: doc, revision } = await withTransition(req, {
9
+ experimentId,
10
+ layerId: relationshipId(before.layer)
11
+ }, async ({ req })=>{
12
+ const current = await readExperiment(req, experimentId);
13
+ const decision = current.decision;
14
+ if (mode === 'stopped' ? current.status !== 'stopped' : current.status !== 'decided') refuse('invalid-status');
15
+ if (mode === 'hard-coded' && current.kind !== 'code') refuse('invalid-status');
16
+ if (mode === 'after-content') {
17
+ const report = decision?.contentReport;
18
+ const docs = Array.isArray(report) ? report : report?.docs;
19
+ if (!Array.isArray(docs) || !docs.every((doc)=>doc?.ok === true)) refuse('invalid-status');
20
+ }
21
+ if (mode === 'discard' && !matchesConfirm(expectedConfirm('discard', ''), confirmation)) refuse('confirmation-required');
22
+ if (mode === 'hard-coded') {
23
+ const winner = current.arms.find((arm)=>arm.key === decision?.winnerArm && arm.kind !== 'holdout');
24
+ if (!winner) refuse('invalid-winner');
25
+ if (!matchesConfirm(expectedConfirm('hard-coded', storedWinnerLabel(winner)), confirmation)) refuse('confirmation-required');
26
+ }
27
+ const at = new Date().toISOString();
28
+ await setPhaseEndedAt(req, experimentId, current.phase, at);
29
+ return updateExperiment(req, experimentId, {
30
+ status: 'archived',
31
+ archivedAt: at,
32
+ layerSlices: [],
33
+ ...mode === 'discard' ? {
34
+ decision: {
35
+ ...decision,
36
+ winnerArm: null,
37
+ discardedAt: at
38
+ }
39
+ } : {}
40
+ });
41
+ });
42
+ return {
43
+ doc,
44
+ revision: revision
45
+ };
46
+ }
@@ -0,0 +1,61 @@
1
+ import type { Arm, DocId, ExperimentKind, ExposurePolicy, FlagPoint, MetricSource, ResolvedOptions, Status, TargetingRule, UnitType } from '../types/contracts.js';
2
+ import type { ArmIssueCode, PreflightResult } from '../types/endpoints.js';
3
+ import type { BlockPlacement, FlagPlacement } from './placements.js';
4
+ export interface StartExperiment {
5
+ id: DocId;
6
+ slug: string;
7
+ status: Status;
8
+ kind: ExperimentKind;
9
+ unitType: UnitType;
10
+ arms: Arm[];
11
+ allocation: number;
12
+ exposurePolicy: ExposurePolicy;
13
+ flagPoint?: string | null;
14
+ layer?: DocId | {
15
+ id: DocId;
16
+ } | null;
17
+ targeting?: TargetingRule[] | null;
18
+ metrics?: {
19
+ primary?: DocId | {
20
+ id: DocId;
21
+ } | null;
22
+ secondary?: Array<DocId | {
23
+ id: DocId;
24
+ }> | null;
25
+ guardrails?: Array<DocId | {
26
+ id: DocId;
27
+ }> | null;
28
+ } | null;
29
+ analysis?: {
30
+ plannedDurationDays?: number | null;
31
+ reachPerDay?: number | null;
32
+ sequentialTuningParameter?: number | null;
33
+ method?: string | null;
34
+ } | null;
35
+ }
36
+ export interface StartMetric {
37
+ id: DocId;
38
+ source: string;
39
+ }
40
+ export interface CheckStartInput {
41
+ experiment: StartExperiment;
42
+ metrics: StartMetric[];
43
+ metricSources?: Pick<MetricSource, 'id' | 'keyedBy'>[];
44
+ flagPoints?: FlagPoint[];
45
+ placements?: BlockPlacement[];
46
+ flagPlacements?: FlagPlacement[];
47
+ placementError?: string;
48
+ flagPlacementError?: string;
49
+ flagPointBusy?: boolean;
50
+ layer?: {
51
+ unitType: UnitType;
52
+ usedSlices: number[];
53
+ } | null;
54
+ attributeKeys?: ResolvedOptions['attributeKeys'];
55
+ }
56
+ export declare const ARM_CHECK_ORDER: readonly ArmIssueCode[];
57
+ export declare const relationshipId: (value: DocId | {
58
+ id: DocId;
59
+ } | null | undefined) => DocId | undefined;
60
+ /** Pure Review/Start predicates in refusal order. Inapplicable checks pass. */
61
+ export declare function checkStart(input: CheckStartInput): PreflightResult['checks'];
@@ -0,0 +1,104 @@
1
+ import { checkArms } from '../collections/experiments/armInvariants.js';
2
+ import { isOpValid } from '../targeting/ops.js';
3
+ export const ARM_CHECK_ORDER = [
4
+ 'weights-total',
5
+ 'control-count',
6
+ 'control-first',
7
+ 'holdout-count',
8
+ 'holdout-last',
9
+ 'variant-count',
10
+ 'invalid-key',
11
+ 'duplicate-key',
12
+ 'slot-required',
13
+ 'duplicate-slot'
14
+ ];
15
+ export const relationshipId = (value)=>value == null ? undefined : typeof value === 'object' ? value.id : value;
16
+ const positive = (value)=>typeof value === 'number' && Number.isFinite(value) && value > 0;
17
+ function invalidParams(arms, point) {
18
+ return arms.flatMap((arm)=>{
19
+ // Holdouts render the control's effective defaults and have no params of their own.
20
+ if (arm.kind === 'holdout') return [];
21
+ const params = arm.params ?? {};
22
+ return Object.entries(point.params).flatMap(([key, field])=>{
23
+ const value = params[key];
24
+ const missing = value == null || value === '';
25
+ const invalid = missing ? field.required === true : field.type === 'number' ? typeof value !== 'number' || !Number.isFinite(value) || field.min !== undefined && value < field.min || field.max !== undefined && value > field.max : field.type === 'checkbox' ? typeof value !== 'boolean' : field.type === 'select' ? !field.options?.some((option)=>option.value === value) : typeof value !== 'string' || field.required === true && !value.trim();
26
+ return invalid ? [
27
+ {
28
+ arm: arm.key,
29
+ field: key
30
+ }
31
+ ] : [];
32
+ });
33
+ });
34
+ }
35
+ /** Pure Review/Start predicates in refusal order. Inapplicable checks pass. */ export function checkStart(input) {
36
+ const { experiment: e } = input;
37
+ const checks = [];
38
+ const add = (code, ok, detail)=>checks.push({
39
+ code,
40
+ ok,
41
+ ...!ok && detail !== undefined ? {
42
+ detail
43
+ } : {}
44
+ });
45
+ add('invalid-status', e.status === 'draft' || e.status === 'stopped');
46
+ const issues = checkArms(e.arms, e.kind);
47
+ for (const code of ARM_CHECK_ORDER){
48
+ const matches = issues.filter((issue)=>issue.code === code);
49
+ add(code, matches.length === 0, matches);
50
+ }
51
+ add('no-planned-duration', positive(e.analysis?.plannedDurationDays));
52
+ const primary = relationshipId(e.metrics?.primary);
53
+ add('no-primary-metric', primary !== undefined && input.metrics.some((metric)=>String(metric.id) === String(primary)));
54
+ add('no-reach-per-day', ![
55
+ 'manual',
56
+ 'viewport'
57
+ ].includes(e.exposurePolicy) || positive(e.analysis?.reachPerDay));
58
+ add('page-no-targets', e.kind !== 'page' || e.arms.filter((arm)=>arm.kind !== 'holdout').every((arm)=>typeof arm.pageTarget === 'string' && arm.pageTarget.trim().length > 0));
59
+ add('page-user-unit', e.kind !== 'page' || e.unitType === 'visitor');
60
+ const point = input.flagPoints?.find((point)=>point.id === e.flagPoint);
61
+ add('flag-no-point', e.kind !== 'flag' || !!point);
62
+ const params = e.kind === 'flag' && point ? invalidParams(e.arms, point) : [];
63
+ add('flag-params-invalid', params.length === 0, params);
64
+ add('flag-point-busy', e.kind !== 'flag' || !input.flagPointBusy);
65
+ const missing = input.flagPlacements === undefined ? [] : e.arms.filter((arm)=>arm.kind !== 'holdout' && !input.flagPlacements.some((placement)=>placement.arm === arm.key && placement.ok));
66
+ const failed = input.flagPlacements?.filter((placement)=>!placement.ok) ?? [];
67
+ add('flag-placement-missing', e.kind !== 'flag' || !input.flagPlacementError && missing.length === 0 && failed.length === 0, input.flagPlacementError ?? {
68
+ missing: missing.map((arm)=>arm.key),
69
+ placements: failed
70
+ });
71
+ const placements = input.placements ?? [];
72
+ add('no-placements', e.kind !== 'block' || !input.placementError && placements.length > 0, input.placementError);
73
+ const empty = placements.filter((placement)=>e.arms.some((arm)=>arm.kind !== 'holdout' && (!arm.slot || !(placement.slots[arm.slot] > 0))));
74
+ add('empty-slot', e.kind !== 'block' || empty.length === 0, empty);
75
+ add('layer-unit-mismatch', e.layer == null || input.layer?.unitType === e.unitType);
76
+ const free = 100 - new Set(input.layer?.usedSlices).size;
77
+ add('layer-full', e.layer == null || !!input.layer && free >= e.allocation, {
78
+ available: free,
79
+ requested: e.allocation
80
+ });
81
+ const unknown = input.metrics.filter((metric)=>!metric.source.startsWith('events:') && !input.metricSources?.some((source)=>source.id === metric.source));
82
+ add('unknown-metric-source', unknown.length === 0, unknown);
83
+ const mismatches = input.metrics.filter((metric)=>!metric.source.startsWith('events:') && e.unitType === 'user' && input.metricSources?.some((source)=>source.id === metric.source && source.keyedBy === 'visitor'));
84
+ add('unit-source-mismatch', mismatches.length === 0, mismatches);
85
+ const undeclared = (e.targeting ?? []).filter((rule)=>!Object.hasOwn(input.attributeKeys ?? {}, rule.attribute));
86
+ add('undeclared-targeting-key', undeclared.length === 0, {
87
+ message: 'Targeting uses an undeclared attribute.',
88
+ attributes: undeclared.map((rule)=>rule.attribute)
89
+ });
90
+ const invalidRules = (e.targeting ?? []).filter((rule)=>{
91
+ const type = Object.hasOwn(input.attributeKeys ?? {}, rule.attribute) ? input.attributeKeys[rule.attribute].type : undefined;
92
+ if (!type) return false // Report undeclared keys through their own refusal.
93
+ ;
94
+ const validValue = type === 'string' && [
95
+ 'in',
96
+ 'nin'
97
+ ].includes(rule.op) ? Array.isArray(rule.value) && rule.value.length > 0 && rule.value.every((value)=>typeof value === 'string') : typeof rule.value === type && (type !== 'number' || Number.isFinite(rule.value));
98
+ return !isOpValid(type, rule.op) || !validValue;
99
+ });
100
+ add('invalid-targeting-rule', invalidRules.length === 0, {
101
+ attributes: invalidRules.map((rule)=>rule.attribute)
102
+ });
103
+ return checks;
104
+ }
@@ -0,0 +1,5 @@
1
+ export declare function expectedConfirm(action: 'decide' | 'discard' | 'hard-coded', winnerLabel: string): string;
2
+ export declare function matchesConfirm(expected: string, typed: string | undefined | null): boolean;
3
+ import type { Arm } from '../types/contracts.js';
4
+ /** Confirmation uses the stored editor label, with fixed vocabulary as fallback. */
5
+ export declare function storedWinnerLabel(arm: Arm): string;
@@ -0,0 +1,18 @@
1
+ export function expectedConfirm(action, winnerLabel) {
2
+ if (action === 'decide') return winnerLabel;
3
+ if (action === 'discard') return 'discard winner';
4
+ return `the developer has hard-coded ${winnerLabel}`;
5
+ }
6
+ export function matchesConfirm(expected, typed) {
7
+ const phrase = expected.trim();
8
+ return phrase !== '' && typed != null && typed.trim() === phrase;
9
+ }
10
+ /** Confirmation uses the stored editor label, with fixed vocabulary as fallback. */ export function storedWinnerLabel(arm) {
11
+ return arm.label?.trim() || ({
12
+ control: 'Original',
13
+ variantA: 'Variant A',
14
+ variantB: 'Variant B',
15
+ variantC: 'Variant C',
16
+ holdout: 'Holdout'
17
+ })[arm.key];
18
+ }
@@ -0,0 +1,9 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { DocId } from '../types/contracts.js';
3
+ import type { DecideBody } from '../types/endpoints.js';
4
+ export declare function decide(req: PayloadRequest, { experimentId, winnerArm, note, confirmation }: DecideBody & {
5
+ experimentId: DocId;
6
+ }): Promise<{
7
+ doc: import("payload").JsonObject & import("payload").TypeWithID;
8
+ revision: number;
9
+ }>;
@@ -0,0 +1,138 @@
1
+ import { reclaimSlices } from '../db/slices.js';
2
+ import { decideGuard } from '../results/decideGuard.js';
3
+ import { relationshipId } from './checkStart.js';
4
+ import { expectedConfirm, matchesConfirm, storedWinnerLabel } from './confirmPhrases.js';
5
+ import { withTransition } from './kernel.js';
6
+ import { readPhaseRecord, setPhaseEndedAt, writePhaseRecord } from './phaseRecord.js';
7
+ import { readExperiment, refuse, updateExperiment } from './start.js';
8
+ export async function decide(req, { experimentId, winnerArm, note, confirmation }) {
9
+ const before = await readExperiment(req, experimentId);
10
+ const { result: doc, revision } = await withTransition(req, {
11
+ experimentId,
12
+ layerId: relationshipId(before.layer),
13
+ flagPointId: before.kind === 'flag' ? before.flagPoint : undefined
14
+ }, async ({ req, exec })=>{
15
+ const current = await readExperiment(req, experimentId);
16
+ if (current.status !== 'running' && current.status !== 'stopped') refuse('invalid-status');
17
+ if (current.kind === 'flag' && current.flagPoint) {
18
+ // §7.8: recheck point occupancy under its lock before restoring live routing.
19
+ const busy = await req.payload.find({
20
+ collection: 'xp-experiments',
21
+ req,
22
+ overrideAccess: true,
23
+ depth: 0,
24
+ limit: 0,
25
+ where: {
26
+ and: [
27
+ {
28
+ flagPoint: {
29
+ equals: current.flagPoint
30
+ }
31
+ },
32
+ {
33
+ status: {
34
+ in: [
35
+ 'running',
36
+ 'decided'
37
+ ]
38
+ }
39
+ }
40
+ ]
41
+ }
42
+ });
43
+ if (busy.docs.some((doc)=>String(doc.id) !== String(experimentId))) refuse('flag-point-busy');
44
+ }
45
+ const test = await readPhaseRecord(req, experimentId, current.phase);
46
+ if (test.phaseKind !== 'test') refuse('invalid-status');
47
+ const winner = test.arms.find((arm)=>arm.key === winnerArm && arm.kind !== 'holdout');
48
+ if (!winner) refuse('invalid-winner');
49
+ // §7.8: decision snapshot lookup serves the authorised transition.
50
+ const snapshots = await req.payload.find({
51
+ collection: 'xp-results-snapshots',
52
+ req,
53
+ overrideAccess: true,
54
+ depth: 0,
55
+ limit: 1,
56
+ where: {
57
+ and: [
58
+ {
59
+ experiment: {
60
+ equals: experimentId
61
+ }
62
+ },
63
+ {
64
+ phase: {
65
+ equals: test.phase
66
+ }
67
+ }
68
+ ]
69
+ },
70
+ sort: [
71
+ '-computedAt',
72
+ '-id'
73
+ ]
74
+ });
75
+ const snapshotDoc = snapshots.docs[0];
76
+ const snapshot = snapshotDoc?.snapshot;
77
+ if (decideGuard(snapshot ?? null, winnerArm) && (!note?.trim() || !matchesConfirm(expectedConfirm('decide', storedWinnerLabel(winner)), confirmation))) refuse('confirmation-required');
78
+ const at = new Date().toISOString();
79
+ const holdout = test.arms.find((arm)=>arm.kind === 'holdout');
80
+ const rollout = holdout !== undefined && winner.kind !== 'control';
81
+ let slices = current.layerSlices ?? [];
82
+ if (rollout && current.status === 'stopped' && relationshipId(current.layer) !== undefined) {
83
+ const original = test.analysis?.layerSlices;
84
+ if (!Array.isArray(original) || original.length !== test.allocation || !original.every((slice)=>Number.isInteger(slice) && slice >= 0 && slice < 100) || new Set(original).size !== original.length) refuse('slices-taken');
85
+ slices = await reclaimSlices(exec, {
86
+ layerId: relationshipId(current.layer),
87
+ experimentId,
88
+ slices: original
89
+ });
90
+ }
91
+ await setPhaseEndedAt(req, experimentId, test.phase, at);
92
+ const phase = rollout ? test.phase + 1 : test.phase;
93
+ if (rollout) {
94
+ const arms = [
95
+ {
96
+ ...winner,
97
+ weight: 100 - holdout.weight
98
+ },
99
+ {
100
+ ...holdout,
101
+ weight: holdout.weight
102
+ }
103
+ ];
104
+ await writePhaseRecord(req, {
105
+ experiment: experimentId,
106
+ phase,
107
+ phaseKind: 'rollout',
108
+ startedAt: at,
109
+ seed: test.seed,
110
+ arms,
111
+ allocation: test.allocation,
112
+ allocationChanges: [],
113
+ targeting: test.targeting,
114
+ metricDefinitions: test.metricDefinitions,
115
+ analysis: test.analysis,
116
+ contentChanges: []
117
+ });
118
+ }
119
+ return updateExperiment(req, experimentId, {
120
+ status: 'decided',
121
+ phase,
122
+ seed: test.seed,
123
+ decidedAt: at,
124
+ layerSlices: rollout ? slices : [],
125
+ decision: {
126
+ winnerArm,
127
+ verdictCode: snapshot?.verdict.code ?? 'unavailable',
128
+ note: note?.trim() ?? null,
129
+ decidedBy: req.user?.id == null ? null : String(req.user.id),
130
+ snapshotId: snapshotDoc ? String(snapshotDoc.id) : null
131
+ }
132
+ });
133
+ });
134
+ return {
135
+ doc,
136
+ revision: revision
137
+ };
138
+ }
@@ -0,0 +1,5 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { DocId } from '../types/contracts.js';
3
+ /** Payload deleteByID owns the request transaction; never commit inside hooks. */
4
+ export declare function deleteExperiment(req: PayloadRequest, experimentId: DocId): Promise<void>;
5
+ export declare function deleteExperimentAfter(req: PayloadRequest, experimentId: DocId): Promise<void>;