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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (466) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +582 -2
  3. package/dist/access.d.ts +1 -0
  4. package/dist/access.js +2 -0
  5. package/dist/admin/AllocationField.d.ts +2 -0
  6. package/dist/admin/AllocationField.js +80 -0
  7. package/dist/admin/FlagPointSelect.d.ts +2 -0
  8. package/dist/admin/FlagPointSelect.js +54 -0
  9. package/dist/admin/LayerUsage.d.ts +2 -0
  10. package/dist/admin/LayerUsage.js +24 -0
  11. package/dist/admin/LockedFieldNotice.d.ts +2 -0
  12. package/dist/admin/LockedFieldNotice.js +26 -0
  13. package/dist/admin/MetricSourceSelect.d.ts +2 -0
  14. package/dist/admin/MetricSourceSelect.js +32 -0
  15. package/dist/admin/PhaseHistory.d.ts +2 -0
  16. package/dist/admin/PhaseHistory.js +76 -0
  17. package/dist/admin/PreviewLinks.d.ts +11 -0
  18. package/dist/admin/PreviewLinks.js +69 -0
  19. package/dist/admin/StatusCell.d.ts +3 -0
  20. package/dist/admin/StatusCell.js +19 -0
  21. package/dist/admin/SummaryCell.d.ts +2 -0
  22. package/dist/admin/SummaryCell.js +32 -0
  23. package/dist/admin/actions/AddMetricDialog.d.ts +6 -0
  24. package/dist/admin/actions/AddMetricDialog.js +138 -0
  25. package/dist/admin/actions/AllocateDialog.d.ts +6 -0
  26. package/dist/admin/actions/AllocateDialog.js +81 -0
  27. package/dist/admin/actions/ConfirmDialog.d.ts +14 -0
  28. package/dist/admin/actions/ConfirmDialog.js +192 -0
  29. package/dist/admin/actions/DecideDialog.d.ts +7 -0
  30. package/dist/admin/actions/DecideDialog.js +173 -0
  31. package/dist/admin/actions/ExperimentActions.d.ts +3 -0
  32. package/dist/admin/actions/ExperimentActions.js +110 -0
  33. package/dist/admin/actions/MakeContentResult.d.ts +6 -0
  34. package/dist/admin/actions/MakeContentResult.js +50 -0
  35. package/dist/admin/actions/actions.model.d.ts +37 -0
  36. package/dist/admin/actions/actions.model.js +71 -0
  37. package/dist/admin/actions/decide.model.d.ts +21 -0
  38. package/dist/admin/actions/decide.model.js +38 -0
  39. package/dist/admin/allocation.model.d.ts +20 -0
  40. package/dist/admin/allocation.model.js +38 -0
  41. package/dist/admin/api.d.ts +32 -0
  42. package/dist/admin/api.js +95 -0
  43. package/dist/admin/armSelect/ArmSelectField.d.ts +2 -0
  44. package/dist/admin/armSelect/ArmSelectField.js +185 -0
  45. package/dist/admin/armSelect/armSelect.model.d.ts +30 -0
  46. package/dist/admin/armSelect/armSelect.model.js +20 -0
  47. package/dist/admin/arms/ArmsField.d.ts +2 -0
  48. package/dist/admin/arms/ArmsField.js +310 -0
  49. package/dist/admin/arms/ParamsEditor.d.ts +16 -0
  50. package/dist/admin/arms/ParamsEditor.js +79 -0
  51. package/dist/admin/arms/arms.model.d.ts +21 -0
  52. package/dist/admin/arms/arms.model.js +153 -0
  53. package/dist/admin/arms/pageTarget.model.d.ts +4 -0
  54. package/dist/admin/arms/pageTarget.model.js +19 -0
  55. package/dist/admin/arms/params.model.d.ts +7 -0
  56. package/dist/admin/arms/params.model.js +35 -0
  57. package/dist/admin/copy/actions.d.ts +38 -0
  58. package/dist/admin/copy/actions.js +38 -0
  59. package/dist/admin/copy/armSelect.d.ts +11 -0
  60. package/dist/admin/copy/armSelect.js +11 -0
  61. package/dist/admin/copy/arms.d.ts +37 -0
  62. package/dist/admin/copy/arms.js +47 -0
  63. package/dist/admin/copy/common.d.ts +20 -0
  64. package/dist/admin/copy/common.js +27 -0
  65. package/dist/admin/copy/history.d.ts +9 -0
  66. package/dist/admin/copy/history.js +9 -0
  67. package/dist/admin/copy/metrics.d.ts +9 -0
  68. package/dist/admin/copy/metrics.js +9 -0
  69. package/dist/admin/copy/results.d.ts +78 -0
  70. package/dist/admin/copy/results.js +81 -0
  71. package/dist/admin/copy/review.d.ts +33 -0
  72. package/dist/admin/copy/review.js +127 -0
  73. package/dist/admin/copy/status.d.ts +8 -0
  74. package/dist/admin/copy/status.js +8 -0
  75. package/dist/admin/copy/tabs.d.ts +7 -0
  76. package/dist/admin/copy/tabs.js +7 -0
  77. package/dist/admin/copy/who.d.ts +36 -0
  78. package/dist/admin/copy/who.js +36 -0
  79. package/dist/admin/labels.d.ts +45 -0
  80. package/dist/admin/labels.js +53 -0
  81. package/dist/admin/layerUsage.model.d.ts +15 -0
  82. package/dist/admin/layerUsage.model.js +18 -0
  83. package/dist/admin/lockedNotice.model.d.ts +3 -0
  84. package/dist/admin/lockedNotice.model.js +18 -0
  85. package/dist/admin/metricSource.model.d.ts +7 -0
  86. package/dist/admin/metricSource.model.js +11 -0
  87. package/dist/admin/paths.d.ts +16 -0
  88. package/dist/admin/paths.js +16 -0
  89. package/dist/admin/permissions.model.d.ts +10 -0
  90. package/dist/admin/permissions.model.js +4 -0
  91. package/dist/admin/phaseHistory.model.d.ts +19 -0
  92. package/dist/admin/phaseHistory.model.js +19 -0
  93. package/dist/admin/reload.d.ts +1 -0
  94. package/dist/admin/reload.js +1 -0
  95. package/dist/admin/results/ArmTable.d.ts +8 -0
  96. package/dist/admin/results/ArmTable.js +112 -0
  97. package/dist/admin/results/CappedCell.d.ts +4 -0
  98. package/dist/admin/results/CappedCell.js +24 -0
  99. package/dist/admin/results/CiChart.d.ts +10 -0
  100. package/dist/admin/results/CiChart.js +178 -0
  101. package/dist/admin/results/DataQualityPanel.d.ts +4 -0
  102. package/dist/admin/results/DataQualityPanel.js +40 -0
  103. package/dist/admin/results/ResultsView.d.ts +3 -0
  104. package/dist/admin/results/ResultsView.js +312 -0
  105. package/dist/admin/results/VerdictCard.d.ts +9 -0
  106. package/dist/admin/results/VerdictCard.js +49 -0
  107. package/dist/admin/results/chart.model.d.ts +32 -0
  108. package/dist/admin/results/chart.model.js +52 -0
  109. package/dist/admin/results/format.d.ts +10 -0
  110. package/dist/admin/results/format.js +55 -0
  111. package/dist/admin/results/results.model.d.ts +105 -0
  112. package/dist/admin/results/results.model.js +175 -0
  113. package/dist/admin/review/ReviewPanel.d.ts +2 -0
  114. package/dist/admin/review/ReviewPanel.js +268 -0
  115. package/dist/admin/review/review.model.d.ts +31 -0
  116. package/dist/admin/review/review.model.js +40 -0
  117. package/dist/admin/status.model.d.ts +16 -0
  118. package/dist/admin/status.model.js +17 -0
  119. package/dist/admin/styles.css +10 -0
  120. package/dist/admin/styles.d.ts +1 -0
  121. package/dist/admin/styles.js +2 -0
  122. package/dist/admin/summaryBatcher.d.ts +8 -0
  123. package/dist/admin/summaryBatcher.js +33 -0
  124. package/dist/admin/targeting/TargetingAttributeField.d.ts +2 -0
  125. package/dist/admin/targeting/TargetingAttributeField.js +31 -0
  126. package/dist/admin/targeting/TargetingOpField.d.ts +2 -0
  127. package/dist/admin/targeting/TargetingOpField.js +45 -0
  128. package/dist/admin/targeting/TargetingValueField.d.ts +2 -0
  129. package/dist/admin/targeting/TargetingValueField.js +142 -0
  130. package/dist/admin/targeting/targeting.model.d.ts +18 -0
  131. package/dist/admin/targeting/targeting.model.js +48 -0
  132. package/dist/admin/useApi.d.ts +20 -0
  133. package/dist/admin/useApi.js +10 -0
  134. package/dist/admin/useLayerUsage.d.ts +15 -0
  135. package/dist/admin/useLayerUsage.js +92 -0
  136. package/dist/client/ExperimentPageview.d.ts +1 -0
  137. package/dist/client/ExperimentPageview.js +27 -0
  138. package/dist/client/ExperimentsProvider.d.ts +6 -0
  139. package/dist/client/ExperimentsProvider.js +22 -0
  140. package/dist/client/ViewportExposure.d.ts +9 -0
  141. package/dist/client/ViewportExposure.js +69 -0
  142. package/dist/client/beacon.d.ts +17 -0
  143. package/dist/client/beacon.js +71 -0
  144. package/dist/client/context.d.ts +2 -0
  145. package/dist/client/context.js +2 -0
  146. package/dist/client/idempotency.d.ts +2 -0
  147. package/dist/client/idempotency.js +13 -0
  148. package/dist/client/trackConversionBeacon.d.ts +3 -0
  149. package/dist/client/trackConversionBeacon.js +16 -0
  150. package/dist/client/useExperiment.d.ts +5 -0
  151. package/dist/client/useExperiment.js +27 -0
  152. package/dist/client/useFlagPoint.d.ts +9 -0
  153. package/dist/client/useFlagPoint.js +32 -0
  154. package/dist/collections/access.d.ts +21 -0
  155. package/dist/collections/access.js +25 -0
  156. package/dist/collections/events.d.ts +3 -0
  157. package/dist/collections/events.js +117 -0
  158. package/dist/collections/experiments/armInvariants.d.ts +12 -0
  159. package/dist/collections/experiments/armInvariants.js +89 -0
  160. package/dist/collections/experiments/config.d.ts +3 -0
  161. package/dist/collections/experiments/config.js +574 -0
  162. package/dist/collections/experiments/createDefaults.d.ts +2 -0
  163. package/dist/collections/experiments/createDefaults.js +7 -0
  164. package/dist/collections/experiments/deleteGuard.d.ts +3 -0
  165. package/dist/collections/experiments/deleteGuard.js +8 -0
  166. package/dist/collections/experiments/editability.d.ts +4 -0
  167. package/dist/collections/experiments/editability.js +35 -0
  168. package/dist/collections/experiments/flagControlParams.d.ts +2 -0
  169. package/dist/collections/experiments/flagControlParams.js +32 -0
  170. package/dist/collections/experiments/lockHook.d.ts +2 -0
  171. package/dist/collections/experiments/lockHook.js +134 -0
  172. package/dist/collections/identities.d.ts +3 -0
  173. package/dist/collections/identities.js +30 -0
  174. package/dist/collections/index.d.ts +6 -0
  175. package/dist/collections/index.js +24 -0
  176. package/dist/collections/layers.d.ts +3 -0
  177. package/dist/collections/layers.js +77 -0
  178. package/dist/collections/metrics.d.ts +3 -0
  179. package/dist/collections/metrics.js +115 -0
  180. package/dist/collections/phases.d.ts +3 -0
  181. package/dist/collections/phases.js +59 -0
  182. package/dist/collections/serialize.d.ts +5 -0
  183. package/dist/collections/serialize.js +40 -0
  184. package/dist/collections/snapshots.d.ts +3 -0
  185. package/dist/collections/snapshots.js +47 -0
  186. package/dist/constants.d.ts +7 -0
  187. package/dist/constants.js +7 -0
  188. package/dist/db/adapter.d.ts +3 -0
  189. package/dist/db/adapter.js +9 -0
  190. package/dist/db/appends.d.ts +58 -0
  191. package/dist/db/appends.js +116 -0
  192. package/dist/db/delete.d.ts +17 -0
  193. package/dist/db/delete.js +35 -0
  194. package/dist/db/erase.d.ts +12 -0
  195. package/dist/db/erase.js +55 -0
  196. package/dist/db/events.d.ts +21 -0
  197. package/dist/db/events.js +100 -0
  198. package/dist/db/executor.d.ts +10 -0
  199. package/dist/db/executor.js +37 -0
  200. package/dist/db/identities.d.ts +11 -0
  201. package/dist/db/identities.js +29 -0
  202. package/dist/db/locks.d.ts +15 -0
  203. package/dist/db/locks.js +23 -0
  204. package/dist/db/manifest.d.ts +8 -0
  205. package/dist/db/manifest.js +8 -0
  206. package/dist/db/names.d.ts +271 -0
  207. package/dist/db/names.js +270 -0
  208. package/dist/db/pgExecutor.d.ts +41 -0
  209. package/dist/db/pgExecutor.js +250 -0
  210. package/dist/db/results.d.ts +53 -0
  211. package/dist/db/results.js +103 -0
  212. package/dist/db/slices.d.ts +18 -0
  213. package/dist/db/slices.js +51 -0
  214. package/dist/db/snapshots.d.ts +47 -0
  215. package/dist/db/snapshots.js +89 -0
  216. package/dist/db/sql.d.ts +5 -0
  217. package/dist/db/sql.js +5 -0
  218. package/dist/db/state.d.ts +12 -0
  219. package/dist/db/state.js +27 -0
  220. package/dist/db/sweep.d.ts +13 -0
  221. package/dist/db/sweep.js +90 -0
  222. package/dist/db/txid.d.ts +7 -0
  223. package/dist/db/txid.js +12 -0
  224. package/dist/define.d.ts +3 -0
  225. package/dist/define.js +6 -0
  226. package/dist/endpoints/events.d.ts +7 -0
  227. package/dist/endpoints/events.js +98 -0
  228. package/dist/endpoints/index.d.ts +18 -0
  229. package/dist/endpoints/index.js +160 -0
  230. package/dist/endpoints/manifest.d.ts +7 -0
  231. package/dist/endpoints/manifest.js +30 -0
  232. package/dist/endpoints/preflight.d.ts +8 -0
  233. package/dist/endpoints/preflight.js +25 -0
  234. package/dist/endpoints/previewToken.d.ts +7 -0
  235. package/dist/endpoints/previewToken.js +54 -0
  236. package/dist/endpoints/recompute.d.ts +7 -0
  237. package/dist/endpoints/recompute.js +41 -0
  238. package/dist/endpoints/results.d.ts +24 -0
  239. package/dist/endpoints/results.js +136 -0
  240. package/dist/endpoints/summary.d.ts +7 -0
  241. package/dist/endpoints/summary.js +40 -0
  242. package/dist/endpoints/transitions.d.ts +42 -0
  243. package/dist/endpoints/transitions.js +154 -0
  244. package/dist/errors.d.ts +27 -0
  245. package/dist/errors.js +33 -0
  246. package/dist/events/body.d.ts +7 -0
  247. package/dist/events/body.js +43 -0
  248. package/dist/events/dimensions.d.ts +4 -0
  249. package/dist/events/dimensions.js +51 -0
  250. package/dist/events/preview.d.ts +2 -0
  251. package/dist/events/preview.js +9 -0
  252. package/dist/events/request.d.ts +3 -0
  253. package/dist/events/request.js +14 -0
  254. package/dist/events/rows.d.ts +7 -0
  255. package/dist/events/rows.js +65 -0
  256. package/dist/exports/adminClient.d.ts +16 -0
  257. package/dist/exports/adminClient.js +17 -0
  258. package/dist/exports/client.d.ts +7 -0
  259. package/dist/exports/client.js +7 -0
  260. package/dist/exports/index.d.ts +4 -0
  261. package/dist/exports/index.js +3 -0
  262. package/dist/exports/proxy.d.ts +4 -0
  263. package/dist/exports/proxy.js +3 -0
  264. package/dist/exports/puck.d.ts +4 -0
  265. package/dist/exports/puck.js +3 -0
  266. package/dist/exports/puckEditor.d.ts +1 -0
  267. package/dist/exports/puckEditor.js +2 -0
  268. package/dist/exports/rsc.d.ts +13 -0
  269. package/dist/exports/rsc.js +13 -0
  270. package/dist/exports/runtimeProxy.d.ts +4 -0
  271. package/dist/exports/runtimeProxy.js +2 -0
  272. package/dist/exports/runtimeServer.d.ts +2 -0
  273. package/dist/exports/runtimeServer.js +1 -0
  274. package/dist/fields/armSelect.d.ts +7 -0
  275. package/dist/fields/armSelect.js +33 -0
  276. package/dist/globals/state.d.ts +3 -0
  277. package/dist/globals/state.js +31 -0
  278. package/dist/hooks/contentChanges.d.ts +3 -0
  279. package/dist/hooks/contentChanges.js +10 -0
  280. package/dist/hooks/hostHook.d.ts +5 -0
  281. package/dist/hooks/hostHook.js +20 -0
  282. package/dist/index.d.ts +1 -0
  283. package/dist/index.js +4 -0
  284. package/dist/manifest/cache.d.ts +10 -0
  285. package/dist/manifest/cache.js +51 -0
  286. package/dist/manifest/compile.d.ts +63 -0
  287. package/dist/manifest/compile.js +237 -0
  288. package/dist/onInit.d.ts +5 -0
  289. package/dist/onInit.js +13 -0
  290. package/dist/options/fromReq.d.ts +3 -0
  291. package/dist/options/fromReq.js +5 -0
  292. package/dist/options.d.ts +9 -0
  293. package/dist/options.js +99 -0
  294. package/dist/plugin.d.ts +7 -0
  295. package/dist/plugin.js +62 -0
  296. package/dist/proxy/apply.d.ts +3 -0
  297. package/dist/proxy/apply.js +5 -0
  298. package/dist/proxy/consentFallback.d.ts +5 -0
  299. package/dist/proxy/consentFallback.js +27 -0
  300. package/dist/proxy/control.d.ts +6 -0
  301. package/dist/proxy/control.js +36 -0
  302. package/dist/proxy/createProxy.d.ts +3 -0
  303. package/dist/proxy/createProxy.js +15 -0
  304. package/dist/proxy/pageRewrite.d.ts +4 -0
  305. package/dist/proxy/pageRewrite.js +41 -0
  306. package/dist/proxy/resolve.d.ts +13 -0
  307. package/dist/proxy/resolve.js +163 -0
  308. package/dist/proxy/skip.d.ts +1 -0
  309. package/dist/proxy/skip.js +9 -0
  310. package/dist/puck/ExperimentPicker.d.ts +5 -0
  311. package/dist/puck/ExperimentPicker.js +88 -0
  312. package/dist/puck/ExperimentSlot.d.ts +3 -0
  313. package/dist/puck/ExperimentSlot.js +16 -0
  314. package/dist/puck/copy.d.ts +14 -0
  315. package/dist/puck/copy.js +14 -0
  316. package/dist/puck/editor/CreateExperimentDrawer.d.ts +5 -0
  317. package/dist/puck/editor/CreateExperimentDrawer.js +120 -0
  318. package/dist/puck/editor/ExperimentSlotEditor.d.ts +3 -0
  319. package/dist/puck/editor/ExperimentSlotEditor.js +225 -0
  320. package/dist/puck/editor/cloneSlot.d.ts +3 -0
  321. package/dist/puck/editor/cloneSlot.js +41 -0
  322. package/dist/puck/editor/copy.d.ts +25 -0
  323. package/dist/puck/editor/copy.js +25 -0
  324. package/dist/puck/editor/editor.model.d.ts +16 -0
  325. package/dist/puck/editor/editor.model.js +33 -0
  326. package/dist/puck/experimentSlotField.d.ts +5 -0
  327. package/dist/puck/experimentSlotField.js +13 -0
  328. package/dist/puck/slot.model.d.ts +2 -0
  329. package/dist/puck/slot.model.js +11 -0
  330. package/dist/puck/slotConfig.d.ts +5 -0
  331. package/dist/puck/slotConfig.js +25 -0
  332. package/dist/results/aggregate.d.ts +34 -0
  333. package/dist/results/aggregate.js +66 -0
  334. package/dist/results/analyse.d.ts +79 -0
  335. package/dist/results/analyse.js +378 -0
  336. package/dist/results/cuped.d.ts +16 -0
  337. package/dist/results/cuped.js +31 -0
  338. package/dist/results/decideGuard.d.ts +3 -0
  339. package/dist/results/decideGuard.js +8 -0
  340. package/dist/results/guardrails.d.ts +17 -0
  341. package/dist/results/guardrails.js +10 -0
  342. package/dist/results/history.d.ts +13 -0
  343. package/dist/results/history.js +39 -0
  344. package/dist/results/identityCredit.d.ts +16 -0
  345. package/dist/results/identityCredit.js +46 -0
  346. package/dist/results/power.d.ts +15 -0
  347. package/dist/results/power.js +60 -0
  348. package/dist/results/rolloutUnits.d.ts +17 -0
  349. package/dist/results/rolloutUnits.js +26 -0
  350. package/dist/results/snapshot.d.ts +9 -0
  351. package/dist/results/snapshot.js +154 -0
  352. package/dist/results/srm.d.ts +9 -0
  353. package/dist/results/srm.js +22 -0
  354. package/dist/results/verdict.d.ts +82 -0
  355. package/dist/results/verdict.js +85 -0
  356. package/dist/runtime/assign.d.ts +23 -0
  357. package/dist/runtime/assign.js +104 -0
  358. package/dist/runtime/bot.d.ts +1 -0
  359. package/dist/runtime/bot.js +7 -0
  360. package/dist/runtime/cookie.d.ts +17 -0
  361. package/dist/runtime/cookie.js +35 -0
  362. package/dist/runtime/eligibility.d.ts +22 -0
  363. package/dist/runtime/eligibility.js +20 -0
  364. package/dist/runtime/header.d.ts +17 -0
  365. package/dist/runtime/header.js +97 -0
  366. package/dist/runtime/loaders.d.ts +17 -0
  367. package/dist/runtime/loaders.js +29 -0
  368. package/dist/runtime/normalisePath.d.ts +7 -0
  369. package/dist/runtime/normalisePath.js +15 -0
  370. package/dist/runtime/previewToken.d.ts +9 -0
  371. package/dist/runtime/previewToken.js +23 -0
  372. package/dist/runtime/proxyRuntime.d.ts +2 -0
  373. package/dist/runtime/proxyRuntime.js +22 -0
  374. package/dist/runtime/seed.d.ts +2 -0
  375. package/dist/runtime/seed.js +4 -0
  376. package/dist/runtime/signing.d.ts +3 -0
  377. package/dist/runtime/signing.js +16 -0
  378. package/dist/runtime/targeting.d.ts +6 -0
  379. package/dist/runtime/targeting.js +9 -0
  380. package/dist/server/context.d.ts +10 -0
  381. package/dist/server/context.js +59 -0
  382. package/dist/server/eraseUser.d.ts +7 -0
  383. package/dist/server/eraseUser.js +4 -0
  384. package/dist/server/getAssignments.d.ts +9 -0
  385. package/dist/server/getAssignments.js +227 -0
  386. package/dist/server/getFlagPoint.d.ts +11 -0
  387. package/dist/server/getFlagPoint.js +36 -0
  388. package/dist/server/headerRevision.d.ts +6 -0
  389. package/dist/server/headerRevision.js +10 -0
  390. package/dist/server/linkIdentity.d.ts +9 -0
  391. package/dist/server/linkIdentity.js +34 -0
  392. package/dist/server/metadata.d.ts +7 -0
  393. package/dist/server/metadata.js +24 -0
  394. package/dist/server/previewDraft.d.ts +7 -0
  395. package/dist/server/previewDraft.js +51 -0
  396. package/dist/server/recordContentChange.d.ts +7 -0
  397. package/dist/server/recordContentChange.js +150 -0
  398. package/dist/server/recordExposures.d.ts +7 -0
  399. package/dist/server/recordExposures.js +56 -0
  400. package/dist/server/request.d.ts +7 -0
  401. package/dist/server/request.js +39 -0
  402. package/dist/server/resolveExperimentData.d.ts +6 -0
  403. package/dist/server/resolveExperimentData.js +64 -0
  404. package/dist/server/runResults.d.ts +22 -0
  405. package/dist/server/runResults.js +307 -0
  406. package/dist/server/serverRuntime.d.ts +7 -0
  407. package/dist/server/serverRuntime.js +20 -0
  408. package/dist/server/sitemap.d.ts +3 -0
  409. package/dist/server/sitemap.js +6 -0
  410. package/dist/server/toClientAssignments.d.ts +7 -0
  411. package/dist/server/toClientAssignments.js +56 -0
  412. package/dist/server/trackConversion.d.ts +12 -0
  413. package/dist/server/trackConversion.js +69 -0
  414. package/dist/targeting/ops.d.ts +8 -0
  415. package/dist/targeting/ops.js +58 -0
  416. package/dist/transitions/addMetric.d.ts +16 -0
  417. package/dist/transitions/addMetric.js +126 -0
  418. package/dist/transitions/allocate.d.ts +9 -0
  419. package/dist/transitions/allocate.js +84 -0
  420. package/dist/transitions/archive.d.ts +9 -0
  421. package/dist/transitions/archive.js +46 -0
  422. package/dist/transitions/checkStart.d.ts +61 -0
  423. package/dist/transitions/checkStart.js +104 -0
  424. package/dist/transitions/confirmPhrases.d.ts +5 -0
  425. package/dist/transitions/confirmPhrases.js +18 -0
  426. package/dist/transitions/decide.d.ts +9 -0
  427. package/dist/transitions/decide.js +138 -0
  428. package/dist/transitions/deleteExperiment.d.ts +5 -0
  429. package/dist/transitions/deleteExperiment.js +66 -0
  430. package/dist/transitions/kernel.d.ts +31 -0
  431. package/dist/transitions/kernel.js +69 -0
  432. package/dist/transitions/makeContent.d.ts +20 -0
  433. package/dist/transitions/makeContent.js +411 -0
  434. package/dist/transitions/phaseRecord.d.ts +27 -0
  435. package/dist/transitions/phaseRecord.js +81 -0
  436. package/dist/transitions/placements.d.ts +26 -0
  437. package/dist/transitions/placements.js +89 -0
  438. package/dist/transitions/preflightData.d.ts +21 -0
  439. package/dist/transitions/preflightData.js +317 -0
  440. package/dist/transitions/releaseHoldout.d.ts +8 -0
  441. package/dist/transitions/releaseHoldout.js +30 -0
  442. package/dist/transitions/resolveCaps.d.ts +28 -0
  443. package/dist/transitions/resolveCaps.js +68 -0
  444. package/dist/transitions/start.d.ts +26 -0
  445. package/dist/transitions/start.js +220 -0
  446. package/dist/transitions/stop.d.ts +8 -0
  447. package/dist/transitions/stop.js +25 -0
  448. package/dist/types/admin.d.ts +20 -0
  449. package/dist/types/admin.js +1 -0
  450. package/dist/types/assignments.d.ts +57 -0
  451. package/dist/types/assignments.js +1 -0
  452. package/dist/types/contracts.d.ts +133 -0
  453. package/dist/types/contracts.js +2 -0
  454. package/dist/types/endpoints.d.ts +135 -0
  455. package/dist/types/endpoints.js +1 -0
  456. package/dist/types/index.d.ts +8 -0
  457. package/dist/types/index.js +8 -0
  458. package/dist/types/manifest.d.ts +42 -0
  459. package/dist/types/manifest.js +1 -0
  460. package/dist/types/puck.d.ts +23 -0
  461. package/dist/types/puck.js +1 -0
  462. package/dist/types/runtime.d.ts +59 -0
  463. package/dist/types/runtime.js +1 -0
  464. package/dist/types/snapshot.d.ts +117 -0
  465. package/dist/types/snapshot.js +1 -0
  466. package/package.json +137 -3
@@ -0,0 +1,17 @@
1
+ import type { Timestamp } from './aggregate.js';
2
+ /** Spec §7.6/§8.1.5 (X1): first-ever rollout exposures inside assignment's exact ranges. */
3
+ export declare function rolloutAnalysisUnits({ exposuresAllPhases, rolloutPhase, seed, testWeights, coverage, layer }: {
4
+ exposuresAllPhases: readonly {
5
+ unitId: string;
6
+ phase: number;
7
+ ts: Timestamp;
8
+ }[];
9
+ rolloutPhase: number;
10
+ seed: string;
11
+ testWeights: readonly number[];
12
+ coverage: number;
13
+ layer?: {
14
+ slug: string;
15
+ slices: readonly number[];
16
+ };
17
+ }): Set<string>;
@@ -0,0 +1,26 @@
1
+ import { hash, layerSlice, rolloutBucketRanges } from '@delmaredigital/experiments-stats';
2
+ import { timestampMs } from './aggregate.js';
3
+ /** Spec §7.6/§8.1.5 (X1): first-ever rollout exposures inside assignment's exact ranges. */ export function rolloutAnalysisUnits({ exposuresAllPhases, rolloutPhase, seed, testWeights, coverage, layer }) {
4
+ if (!seed.endsWith('|')) throw new RangeError('Assignment seed must end in |');
5
+ const first = new Map();
6
+ for (const row of exposuresAllPhases){
7
+ const ts = timestampMs(row.ts);
8
+ const previous = first.get(row.unitId);
9
+ // Phase numbers increase chronologically; prefer the older phase on a timestamp tie.
10
+ if (!previous || ts < previous.ts || ts === previous.ts && row.phase < previous.phase) {
11
+ first.set(row.unitId, {
12
+ phase: row.phase,
13
+ ts
14
+ });
15
+ }
16
+ }
17
+ const { winner, holdout } = rolloutBucketRanges(testWeights, coverage);
18
+ const units = new Set();
19
+ for (const [unitId, exposure] of first){
20
+ if (exposure.phase !== rolloutPhase) continue;
21
+ if (layer && !layer.slices.includes(layerSlice(layer.slug, unitId))) continue;
22
+ const bucket = hash(seed, unitId, 2);
23
+ if (bucket >= winner[0] && bucket < winner[1] || bucket >= holdout[0] && bucket < holdout[1]) units.add(unitId);
24
+ }
25
+ return units;
26
+ }
@@ -0,0 +1,9 @@
1
+ import type { Snapshot } from '../types/snapshot.js';
2
+ /** JSON cannot represent unbounded confidence limits. */
3
+ export declare function jsonInterval(ci: readonly [number | null, number | null]): [number | null, number | null];
4
+ /** Validate the frozen JSON shape and headline/rollout invariants before storage.
5
+ * This also accepts the shared CORE-13/admin fixtures without any I/O.
6
+ */
7
+ export declare function validateSnapshot(value: unknown): asserts value is Snapshot;
8
+ /** Assemble the already-computed snapshot at the pure/I/O boundary. */
9
+ export declare function assembleSnapshot(snapshot: Snapshot): Snapshot;
@@ -0,0 +1,154 @@
1
+ /** JSON cannot represent unbounded confidence limits. */ export function jsonInterval(ci) {
2
+ return ci.map((bound)=>bound !== null && Number.isFinite(bound) ? bound : null);
3
+ }
4
+ const number = (value)=>typeof value === 'number' && Number.isFinite(value);
5
+ const count = (value)=>number(value) && Number.isInteger(value) && value >= 0;
6
+ const string = (value)=>typeof value === 'string';
7
+ const boolean = (value)=>typeof value === 'boolean';
8
+ const nullable = (check)=>(value)=>value === null || check(value);
9
+ const list = (check)=>(value)=>Array.isArray(value) && value.every(check);
10
+ const oneOf = (...values)=>(value)=>values.includes(value);
11
+ const isObject = (value)=>value !== null && typeof value === 'object' && !Array.isArray(value);
12
+ const object = (fields, optional = {})=>(value)=>isObject(value) && Object.entries(fields).every(([key, check])=>check(value[key])) && Object.entries(optional).every(([key, check])=>value[key] === undefined || check(value[key]));
13
+ const record = (check)=>(value)=>isObject(value) && Object.values(value).every(check);
14
+ const id = (value)=>string(value) || number(value);
15
+ const interval = (value)=>Array.isArray(value) && value.length === 2 && value.every(nullable(number));
16
+ const timestamp = (value)=>typeof value === 'string' && Number.isFinite(new Date(value).getTime());
17
+ const probability = (value)=>number(value) && value >= 0 && value <= 1;
18
+ const status = oneOf('failing', 'ok', 'warning', 'insufficient');
19
+ const shape = object({
20
+ experiment: id,
21
+ phase: count,
22
+ phaseKind: oneOf('test', 'rollout'),
23
+ computedAt: timestamp,
24
+ winnerArm: nullable(string),
25
+ minExposuresPerArm: count,
26
+ window: object({
27
+ from: timestamp,
28
+ to: timestamp
29
+ }),
30
+ method: oneOf('sequential', 'bayesian'),
31
+ alpha: probability,
32
+ alphaAdjusted: probability,
33
+ k: count,
34
+ params: object({
35
+ sequentialTuningParameter: number,
36
+ prior: object({
37
+ mean: number,
38
+ variance: number,
39
+ proper: boolean
40
+ }),
41
+ cupedLookbackDays: number,
42
+ caps: record(object({
43
+ type: oneOf('percentile', 'absolute'),
44
+ value: number,
45
+ resolvedCap: nullable(number)
46
+ }, {
47
+ historyUnits: count
48
+ })),
49
+ attributionWindows: record(number),
50
+ guardrailThresholds: record(number),
51
+ minSampleSizes: record(count),
52
+ bayesian: object({
53
+ ctw: probability,
54
+ risk: number
55
+ })
56
+ }),
57
+ exposures: object({
58
+ total: count,
59
+ perArm: record(count),
60
+ excludedMultiArm: count,
61
+ multiArmShare: probability,
62
+ armMismatch: count,
63
+ identityConflictShare: probability,
64
+ capped: record(count)
65
+ }),
66
+ srm: object({
67
+ p: probability,
68
+ flagged: boolean,
69
+ expected: record(probability)
70
+ }),
71
+ metrics: list(object({
72
+ metricId: id,
73
+ name: string,
74
+ kind: oneOf('binomial', 'count', 'revenue'),
75
+ role: oneOf('primary', 'secondary', 'guardrail'),
76
+ cupedApplied: boolean,
77
+ perArm: list(object({
78
+ arm: string,
79
+ n: count,
80
+ converters: count,
81
+ sum: number,
82
+ sumSquares: number,
83
+ mean: number,
84
+ variance: number
85
+ }, {
86
+ covariate: object({
87
+ preSum: number,
88
+ preSumSquares: number,
89
+ postPreSumOfProducts: number
90
+ }),
91
+ capped: object({
92
+ resolvedCap: number,
93
+ uncappedTotal: number,
94
+ cappedCount: count
95
+ })
96
+ })),
97
+ comparisons: list(object({
98
+ arm: string,
99
+ lift: number,
100
+ ci: interval,
101
+ ciAdjusted: interval,
102
+ significant: oneOf('better', 'worse', 'none')
103
+ }, {
104
+ pValue: probability,
105
+ pValueAdjusted: probability,
106
+ chanceToWin: probability,
107
+ risk: number
108
+ }))
109
+ }, {
110
+ unavailable: string
111
+ })),
112
+ guardrails: list(object({
113
+ metricId: id,
114
+ status
115
+ })),
116
+ power: object({
117
+ daysRemaining: nullable(number),
118
+ exposuresNeededPerArm: nullable(number),
119
+ lowerBound: boolean,
120
+ feasible: boolean
121
+ }),
122
+ verdict: object({
123
+ row: oneOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 'R1', 'R2', 'R3'),
124
+ code: oneOf('unavailable', 'data-problem', 'guardrail-failing', 'collecting', 'original-better', 'worse-none-better', 'winner', 'mixed', 'keep-running', 'no-difference', 'rollout-collecting', 'rollout-readout'),
125
+ finding: string,
126
+ action: nullable(string),
127
+ headline: string,
128
+ detail: string,
129
+ recommendedArm: nullable(string)
130
+ }, {
131
+ guardrailLine: string
132
+ }),
133
+ contentChanges: list(object({
134
+ at: timestamp,
135
+ doc: object({
136
+ collection: string,
137
+ id
138
+ }),
139
+ by: nullable(id)
140
+ }))
141
+ });
142
+ /** Validate the frozen JSON shape and headline/rollout invariants before storage.
143
+ * This also accepts the shared CORE-13/admin fixtures without any I/O.
144
+ */ export function validateSnapshot(value) {
145
+ if (!shape(value)) throw new TypeError('Invalid Snapshot shape (all numbers must be finite)');
146
+ const snapshot = value;
147
+ if (snapshot.phaseKind === 'test' && snapshot.winnerArm !== null || snapshot.phaseKind === 'rollout' && (snapshot.winnerArm === null || !(snapshot.winnerArm in snapshot.exposures.perArm)) || snapshot.verdict.headline !== snapshot.verdict.finding + (snapshot.verdict.action ? ` ${snapshot.verdict.action}` : '') || new Date(snapshot.window.to).getTime() < new Date(snapshot.window.from).getTime()) {
148
+ throw new TypeError('Invalid Snapshot invariants');
149
+ }
150
+ }
151
+ /** Assemble the already-computed snapshot at the pure/I/O boundary. */ export function assembleSnapshot(snapshot) {
152
+ validateSnapshot(snapshot);
153
+ return snapshot;
154
+ }
@@ -0,0 +1,9 @@
1
+ import type { Snapshot } from '../types/snapshot.js';
2
+ /** SRM uses exposed units and renormalised frozen weights, never allocation or
3
+ * assignment counts. Rollout callers pass only shipped/holdout weights (100-h,h).
4
+ * Zero-weight arms are omitted from checkSrm as required by its public contract.
5
+ */
6
+ export declare function exposureSrm(arms: readonly {
7
+ key: string;
8
+ weight: number;
9
+ }[], perArm: Readonly<Record<string, number>>): Snapshot['srm'];
@@ -0,0 +1,22 @@
1
+ import { checkSrm } from '@delmaredigital/experiments-stats';
2
+ /** SRM uses exposed units and renormalised frozen weights, never allocation or
3
+ * assignment counts. Rollout callers pass only shipped/holdout weights (100-h,h).
4
+ * Zero-weight arms are omitted from checkSrm as required by its public contract.
5
+ */ export function exposureSrm(arms, perArm) {
6
+ const active = arms.filter((arm)=>arm.weight > 0);
7
+ const weight = active.reduce((sum, arm)=>sum + arm.weight, 0);
8
+ if (weight <= 0) throw new RangeError('SRM requires positive weights');
9
+ const expected = Object.fromEntries(arms.map((arm)=>[
10
+ arm.key,
11
+ arm.weight / weight
12
+ ]));
13
+ // A unit exposed to an impossible arm is a splitting problem even though
14
+ // checkSrm deliberately omits zero-weight groups from its degrees of freedom.
15
+ const impossible = arms.some((arm)=>arm.weight === 0 && (perArm[arm.key] ?? 0) > 0);
16
+ const p = impossible ? 0 : active.length < 2 ? 1 : checkSrm(active.map((arm)=>perArm[arm.key] ?? 0), active.map((arm)=>arm.weight));
17
+ return {
18
+ p,
19
+ flagged: p < 0.001,
20
+ expected
21
+ };
22
+ }
@@ -0,0 +1,82 @@
1
+ import type { Snapshot } from '../types/snapshot.js';
2
+ /** Already-computed results. Rollouts require the shipped winner's key and label;
3
+ * R3 reads that winner's comparison, regardless of comparison order. */
4
+ export type VerdictInput = VerdictInputFields & ({
5
+ phaseKind: 'test';
6
+ winnerArm?: string;
7
+ winnerLabel?: string;
8
+ } | {
9
+ phaseKind: 'rollout';
10
+ winnerArm: string;
11
+ winnerLabel: string;
12
+ });
13
+ interface VerdictInputFields {
14
+ status: 'running' | 'stopped' | 'decided';
15
+ method: 'sequential' | 'bayesian';
16
+ primary: {
17
+ name: string;
18
+ direction: 'increase' | 'decrease';
19
+ unavailable?: string;
20
+ /** Minimums apply per compared group, excluding the holdout in a test and
21
+ * including it in a rollout. Copy uses the smallest converter count. */
22
+ convertersPerArm: Array<{
23
+ arm: string;
24
+ kind: 'control' | 'variant' | 'holdout';
25
+ converters: number;
26
+ }>;
27
+ minSampleSize: number;
28
+ /** Include every computed comparison. Multiple winners use "Both" for two
29
+ * and "All N variants" for three or more; mixed/worse copy names each result. */
30
+ comparisons: Array<{
31
+ arm: string;
32
+ label: string;
33
+ significant: 'better' | 'worse' | 'none';
34
+ lift: number;
35
+ /** Snapshot ciAdjusted: alpha/k in a test; raw interval in a rollout (k=1). */
36
+ ci: [number | null, number | null];
37
+ /** Probability in [0, 1]; copy floors whole percentages, uses "more than
38
+ * 99%" at >=99.5%, and omits the parenthesis when unavailable. */
39
+ chanceToWin?: number;
40
+ }>;
41
+ };
42
+ exposures: {
43
+ perArm: Array<{
44
+ arm: string;
45
+ label: string;
46
+ kind: 'control' | 'variant' | 'holdout';
47
+ n: number;
48
+ }>;
49
+ minExposuresPerArm: number;
50
+ multiArmShare: number;
51
+ };
52
+ srm: {
53
+ flagged: boolean;
54
+ };
55
+ /** One status per guardrail across variants, holdout excluded: worst wins
56
+ * (failing > warning > insufficient > ok). A failing result names the variant
57
+ * that triggered it. R3 uses this order, with explicit harm wording for failing;
58
+ * no configured guardrails means no guardrailLine, never a claim of safety. */
59
+ guardrails: Array<{
60
+ name: string;
61
+ } & ({
62
+ status: 'failing';
63
+ worstArmLabel: string;
64
+ } | {
65
+ status: 'ok' | 'warning' | 'insufficient';
66
+ worstArmLabel?: string;
67
+ })>;
68
+ /** Additional days needed, rounded up by CORE-15a: 0 = already enough data,
69
+ * null = unknown. Infeasible or >90 days uses the long-duration copy; positive
70
+ * days use an estimate; 0/null with feasible=true runs to the planned end date. */
71
+ power: {
72
+ daysRemaining: number | null;
73
+ feasible: boolean;
74
+ };
75
+ plannedDurationPassed: boolean;
76
+ /** Reserved for future use; "the Original" remains literal UI vocabulary. */
77
+ originalLabel: string;
78
+ controlArm: string;
79
+ }
80
+ /** Apply spec §8.5 in order to already-computed results; no inference is performed here. */
81
+ export declare function computeVerdict(input: VerdictInput): Snapshot['verdict'];
82
+ export {};
@@ -0,0 +1,85 @@
1
+ function percent(value) {
2
+ if (value === null) return '…';
3
+ const rounded = Math.round(value * 100);
4
+ return `${rounded >= 0 ? '+' : ''}${rounded}%`;
5
+ }
6
+ function guardrailLine(input) {
7
+ if (input.guardrails.length === 0) return undefined;
8
+ const failing = input.guardrails.find((item)=>item.status === 'failing');
9
+ if (failing) return `Harm detected: ${failing.name} is worse since shipping.`;
10
+ const warning = input.guardrails.find((item)=>item.status === 'warning');
11
+ if (warning) return `Watch ${warning.name}: trending worse.`;
12
+ if (input.guardrails.some((item)=>item.status === 'insufficient')) return 'Too little data to rule out harm';
13
+ return 'No harm detected so far';
14
+ }
15
+ function labels(items) {
16
+ return items.length < 2 ? items[0].label : `${items.slice(0, -1).map((item)=>item.label).join(', ')} and ${items.at(-1).label}`;
17
+ }
18
+ function chance(value) {
19
+ if (value === undefined) return '';
20
+ return value >= 0.995 ? ' (more than 99%)' : ` (${Math.floor(value * 100)}%)`;
21
+ }
22
+ /** Apply spec §8.5 in order to already-computed results; no inference is performed here. */ export function computeVerdict(input) {
23
+ const rollout = input.phaseKind === 'rollout';
24
+ const make = (row, code, finding, action, detail, recommendedArm, line)=>({
25
+ row,
26
+ code,
27
+ finding,
28
+ action,
29
+ headline: finding + (action ? ` ${action}` : ''),
30
+ detail,
31
+ recommendedArm,
32
+ ...line === undefined ? {} : {
33
+ guardrailLine: line
34
+ }
35
+ });
36
+ if (input.primary.unavailable !== undefined) {
37
+ return make(rollout ? 'R1' : 1, 'unavailable', `Results unavailable: ${input.primary.name} could not be read.`, 'Recompute; if it persists, contact a developer.', 'The main outcome could not be loaded.', null);
38
+ }
39
+ if (input.srm.flagged || input.exposures.multiArmShare >= 0.01) {
40
+ return make(rollout ? 'R1' : 2, 'data-problem', "Data problem: traffic isn't splitting as set.", "Don't trust these results. Check for caching or a page placed twice; contact a developer.", 'The people counted do not match the expected split.', null);
41
+ }
42
+ const compared = input.exposures.perArm.filter((item)=>rollout || item.kind !== 'holdout');
43
+ const minimum = compared.length ? Math.min(...compared.map((item)=>item.n)) : 0;
44
+ const converters = input.primary.convertersPerArm.filter((item)=>rollout || item.kind !== 'holdout');
45
+ const minimumConverters = converters.length ? Math.min(...converters.map((item)=>item.converters)) : 0;
46
+ const collecting = minimum < input.exposures.minExposuresPerArm || minimumConverters < input.primary.minSampleSize;
47
+ if (input.phaseKind === 'rollout') {
48
+ const winner = input.winnerLabel;
49
+ if (collecting) return make('R2', 'rollout-collecting', `Since shipping ${winner}: collecting data (${minimum} of ${input.exposures.minExposuresPerArm} people, ${minimumConverters} of ${input.primary.minSampleSize} conversions).`, null, 'More people and conversions are needed to describe the shipped result.', null);
50
+ const comparison = input.primary.comparisons.find((item)=>item.arm === input.winnerArm);
51
+ const lift = comparison?.lift ?? null;
52
+ const ci = comparison?.ci ?? [
53
+ null,
54
+ null
55
+ ];
56
+ return make('R3', 'rollout-readout', `Since shipping ${winner}: ${percent(lift)} vs the holdout (CI ${percent(ci[0])} to ${percent(ci[1])}).`, null, 'This describes the shipped version compared with people still seeing the Original.', null, guardrailLine(input));
57
+ }
58
+ const failing = input.guardrails.find((item)=>item.status === 'failing');
59
+ if (failing) return make(3, 'guardrail-failing', `Stop: ${failing.worstArmLabel} harms ${failing.name}.`, null, 'An outcome that must stay safe has become worse.', input.controlArm);
60
+ if (collecting) return make(4, 'collecting', input.status === 'stopped' ? 'Stopped before enough data was collected.' : `Collecting data: ${minimum} of ${input.exposures.minExposuresPerArm} people, ${minimumConverters} of ${input.primary.minSampleSize} conversions.`, null, 'More people or conversions are needed before drawing a conclusion.', null);
61
+ const variantKeys = new Set(compared.filter((item)=>item.kind === 'variant').map((item)=>item.arm));
62
+ const variants = input.primary.comparisons.filter((item)=>variantKeys.has(item.arm));
63
+ const better = variants.filter((item)=>item.significant === 'better');
64
+ const worse = variants.filter((item)=>item.significant === 'worse');
65
+ const direction = input.primary.direction === 'decrease' ? -1 : 1;
66
+ const best = [
67
+ ...better
68
+ ].sort((a, b)=>direction * (b.lift - a.lift))[0];
69
+ const worst = worse[0];
70
+ if (variants.length > 0 && worse.length === variants.length) return make(5, 'original-better', 'The Original is better.', 'Decide for the Original.', 'Every tested alternative performed worse.', input.controlArm);
71
+ if (worst && !best) return make(6, 'worse-none-better', `${labels(worse)} ${worse.length === 1 ? 'is' : 'are'} worse; no variant is better.`, `Decide for the Original or restart without ${worse.length === 1 ? 'it' : 'them'}.`, 'At least one alternative performed worse and none improved the main outcome.', input.controlArm);
72
+ if (best && !worst) {
73
+ const finding = better.length >= 2 ? `${better.length === 2 ? 'Both' : `All ${better.length} variants`} beat the Original; not tested against each other — ${best.label} has the larger improvement.` : input.method === 'bayesian' ? `${best.label} is very likely better${chance(best.chanceToWin)}.` : `${best.label} is statistically better: ${percent(best.lift)} (CI ${percent(best.ci[0])} to ${percent(best.ci[1])}).`;
74
+ return make(7, 'winner', finding, null, 'The main outcome improved compared with the Original.', best.arm, guardrailLine(input));
75
+ }
76
+ if (best && worst) return make(8, 'mixed', `Mixed: ${labels(better)} better, ${labels(worse)} worse.`, 'Review before deciding.', 'The tested alternatives moved the main outcome in different directions.', best.arm);
77
+ if (input.status === 'running' && !input.plannedDurationPassed) {
78
+ if (input.power.feasible === false || input.power.daysRemaining !== null && input.power.daysRemaining > 90) {
79
+ return make(9, 'keep-running', 'A clear answer would take more than 90 days at this traffic.', 'Consider a bigger change or a different metric.', 'Current traffic is too low for a timely answer.', null);
80
+ }
81
+ const action = input.power.daysRemaining !== null && input.power.daysRemaining >= 1 ? `Keep running about ${Math.ceil(input.power.daysRemaining)} days.` : 'Keep running to the planned end date.';
82
+ return make(9, 'keep-running', 'No clear winner yet.', action, 'The current results do not yet distinguish the tested versions.', null);
83
+ }
84
+ return make(10, 'no-difference', 'No detectable difference at this size.', null, 'The data collected did not show a clear improvement or decline.', input.controlArm);
85
+ }
@@ -0,0 +1,23 @@
1
+ import type { Assignments } from '../types/assignments.js';
2
+ import type { Attributes } from '../types/contracts.js';
3
+ import type { Manifest } from '../types/manifest.js';
4
+ import { type AssignmentUnit } from './eligibility.js';
5
+ import type { AttributeKeys } from './targeting.js';
6
+ /** Deferred slugs travel on the wire and are evaluated after server session resolution. */
7
+ export interface AssignmentResult extends Assignments {
8
+ deferred: string[];
9
+ }
10
+ export interface AssignOptions {
11
+ now: number;
12
+ staff?: boolean;
13
+ bot?: boolean;
14
+ preview?: boolean;
15
+ sessionUnknown?: boolean;
16
+ method?: string;
17
+ attributeKeys?: AttributeKeys;
18
+ onInvalid?(slug: string, reason: string): void;
19
+ }
20
+ /** Also used after header decoding so flag results always reflect the rendered content. */
21
+ export declare function fillFlagPoints(result: Assignments, manifest: Manifest): void;
22
+ /** Pure shared assignment path; time is supplied by the request's caller. */
23
+ export declare function assign(manifest: Manifest, unit: AssignmentUnit, attributes: Attributes, opts: AssignOptions): AssignmentResult;
@@ -0,0 +1,104 @@
1
+ import { assignArm, hash, inRange, rolloutBucketRanges } from '@delmaredigital/experiments-stats';
2
+ import { eligibility } from './eligibility.js';
3
+ /** Also used after header decoding so flag results always reflect the rendered content. */ export function fillFlagPoints(result, manifest) {
4
+ for (const [id, value] of Object.entries(manifest.flagDefaults)){
5
+ result.flagPoints[id] = {
6
+ arm: value.winnerArm,
7
+ params: {
8
+ ...value.params
9
+ },
10
+ source: 'default',
11
+ exposable: false
12
+ };
13
+ }
14
+ for (const experiment of manifest.experiments){
15
+ const assignment = result.bySlug[experiment.slug];
16
+ if (!experiment.flagPoint || !assignment) continue;
17
+ const contentArm = experiment.arms.find((arm)=>arm.key === assignment.arm && arm.kind !== 'holdout') ?? experiment.arms.find((arm)=>arm.kind === 'control');
18
+ result.flagPoints[experiment.flagPoint] = {
19
+ arm: assignment.arm,
20
+ params: {
21
+ ...contentArm?.params
22
+ },
23
+ source: 'experiment',
24
+ experiment: experiment.slug,
25
+ experimentId: experiment.id,
26
+ phase: experiment.phase,
27
+ policy: assignment.policy,
28
+ exposable: assignment.exposable
29
+ };
30
+ }
31
+ }
32
+ /** Pure shared assignment path; time is supplied by the request's caller. */ export function assign(manifest, unit, attributes, opts) {
33
+ const result = {
34
+ revision: manifest.revision,
35
+ visitorId: unit.visitorId,
36
+ userId: unit.userId,
37
+ bySlug: Object.create(null),
38
+ flagPoints: Object.create(null),
39
+ archivedWinners: {
40
+ ...manifest.archivedWinners
41
+ },
42
+ deferred: [],
43
+ staff: opts.staff ?? attributes.staff === true,
44
+ bot: opts.bot ?? false,
45
+ preview: opts.preview ?? false,
46
+ sessionUnknown: opts.sessionUnknown ?? unit.sessionUnknown ?? false,
47
+ method: opts.method ?? 'GET'
48
+ };
49
+ for (const experiment of manifest.experiments){
50
+ if (experiment.status !== 'running' && experiment.status !== 'decided') continue;
51
+ if ((experiment.status === 'decided' || experiment.phaseKind === 'rollout') && !experiment.arms.some((arm)=>arm.key === experiment.winnerArm)) {
52
+ opts.onInvalid?.(experiment.slug, 'invalid-winner-arm');
53
+ continue;
54
+ }
55
+ if (experiment.phaseKind === 'rollout' && !experiment.arms.some((arm)=>arm.key === experiment.holdoutKey)) {
56
+ opts.onInvalid?.(experiment.slug, 'invalid-holdout-key');
57
+ continue;
58
+ }
59
+ const eligible = eligibility(experiment, unit, attributes, {
60
+ ...opts,
61
+ sessionUnknown: result.sessionUnknown
62
+ });
63
+ if (eligible.kind === 'deferred') {
64
+ result.deferred.push(experiment.slug);
65
+ continue;
66
+ }
67
+ const control = experiment.arms.find((arm)=>arm.kind === 'control');
68
+ let arm = control.key, exposable = false;
69
+ if (eligible.kind === 'eligible') {
70
+ if (experiment.status === 'decided' && !experiment.holdoutKey) {
71
+ arm = experiment.winnerArm;
72
+ } else if (experiment.phaseKind === 'rollout') {
73
+ if (!experiment.seed.endsWith('|')) throw new RangeError('Assignment seed must end in |');
74
+ const ranges = rolloutBucketRanges(experiment.arms.map((arm)=>arm.weight), eligible.coverage);
75
+ const bucket = hash(experiment.seed, eligible.unitId, 2);
76
+ const holdout = eligible.inLayer && inRange(bucket, ranges.holdout);
77
+ arm = holdout ? experiment.holdoutKey : experiment.winnerArm;
78
+ exposable = eligible.inLayer && (holdout || inRange(bucket, ranges.winner));
79
+ } else if (eligible.inLayer) {
80
+ const index = assignArm({
81
+ seed: experiment.seed,
82
+ unitId: eligible.unitId,
83
+ weights: experiment.arms.map((arm)=>arm.weight),
84
+ coverage: eligible.coverage
85
+ });
86
+ if (index !== -1) {
87
+ arm = experiment.arms[index].key;
88
+ exposable = true;
89
+ }
90
+ }
91
+ }
92
+ result.bySlug[experiment.slug] = {
93
+ slug: experiment.slug,
94
+ experimentId: experiment.id,
95
+ arm,
96
+ phase: experiment.phase,
97
+ exposable: exposable && !result.staff && !result.bot && !result.preview && result.method.toUpperCase() !== 'HEAD',
98
+ policy: experiment.exposurePolicy,
99
+ source: eligible.kind === 'ineligible' ? 'fallback' : 'recomputed'
100
+ };
101
+ }
102
+ fillFlagPoints(result, manifest);
103
+ return result;
104
+ }
@@ -0,0 +1 @@
1
+ export declare function isBot(userAgent: string | null | undefined): boolean;
@@ -0,0 +1,7 @@
1
+ // Spec §13 Q13: built-in UA list only, with no external lookup or dependency.
2
+ // `bot` is matched as a suffix of a crawler name (AhrefsBot, GPTBot, AdsBot-Google) but not
3
+ // inside device names (CUBOT); the Pinterest app is not a bot, Pinterestbot is.
4
+ const BOT_UA = /(?<!cu)bot\b|crawler|spider|slurp|bingpreview|facebookexternalhit|facebot|whatsapp|google-inspectiontool|headlesschrome|lighthouse|pagespeed|pingdom|uptimerobot|\bcurl\b|\bwget\b|python-requests|go-http-client/i;
5
+ export function isBot(userAgent) {
6
+ return typeof userAgent === 'string' && BOT_UA.test(userAgent);
7
+ }
@@ -0,0 +1,17 @@
1
+ import { NextRequest } from 'next/server.js';
2
+ import type { ExperimentsProxyRuntime } from '../types/runtime.js';
3
+ export declare function mintUnitId(): string;
4
+ export declare function encodeUnitCookie(unitId: string, secrets: readonly string[]): string;
5
+ export declare function verifyUnitCookie(cookie: string | undefined, secrets: readonly string[]): string | null;
6
+ /** Use Next's cookie selection everywhere: the last cookie of a given name wins. */
7
+ export declare function readUnitCookie(input: Headers | NextRequest, runtime: Pick<ExperimentsProxyRuntime, 'cookie' | 'secrets'>): string | null;
8
+ export declare function cookieAttributes(runtime: Pick<ExperimentsProxyRuntime, 'cookie'>, { https }: {
9
+ https: boolean;
10
+ }): {
11
+ domain?: string | undefined;
12
+ secure?: boolean | undefined;
13
+ httpOnly: boolean;
14
+ sameSite: "lax";
15
+ path: string;
16
+ maxAge: number;
17
+ };
@@ -0,0 +1,35 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { NextRequest } from 'next/server.js';
3
+ import { sign, verify } from './signing.js';
4
+ export function mintUnitId() {
5
+ return randomBytes(16).toString('base64url');
6
+ }
7
+ export function encodeUnitCookie(unitId, secrets) {
8
+ if (!/^[A-Za-z0-9_-]+$/.test(unitId)) throw new Error('Invalid unit id');
9
+ return `${unitId}.${sign(`xp:unit:${unitId}`, secrets)}`;
10
+ }
11
+ export function verifyUnitCookie(cookie, secrets) {
12
+ if (typeof cookie !== 'string') return null;
13
+ const match = /^([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]{43})$/.exec(cookie);
14
+ return match && verify(`xp:unit:${match[1]}`, match[2], secrets) ? match[1] : null;
15
+ }
16
+ /** Use Next's cookie selection everywhere: the last cookie of a given name wins. */ export function readUnitCookie(input, runtime) {
17
+ const request = input instanceof Headers ? new NextRequest('https://xp.invalid/', {
18
+ headers: input
19
+ }) : input;
20
+ return verifyUnitCookie(request.cookies.get(runtime.cookie.name)?.value, runtime.secrets);
21
+ }
22
+ export function cookieAttributes(runtime, { https }) {
23
+ return {
24
+ httpOnly: true,
25
+ sameSite: 'lax',
26
+ path: '/',
27
+ maxAge: runtime.cookie.maxAgeDays * 86400,
28
+ ...https ? {
29
+ secure: true
30
+ } : {},
31
+ ...runtime.cookie.domain ? {
32
+ domain: runtime.cookie.domain
33
+ } : {}
34
+ };
35
+ }
@@ -0,0 +1,22 @@
1
+ import type { Attributes } from '../types/contracts.js';
2
+ import type { ManifestExperiment } from '../types/manifest.js';
3
+ import { type AttributeKeys } from './targeting.js';
4
+ export interface AssignmentUnit {
5
+ visitorId?: string;
6
+ userId?: string;
7
+ sessionUnknown?: boolean;
8
+ }
9
+ export type Eligibility = {
10
+ kind: 'deferred';
11
+ } | {
12
+ kind: 'ineligible';
13
+ } | {
14
+ kind: 'eligible';
15
+ unitId: string;
16
+ inLayer: boolean;
17
+ coverage: number;
18
+ };
19
+ export declare function eligibility(experiment: ManifestExperiment, unit: AssignmentUnit, attributes: Attributes, opts?: {
20
+ sessionUnknown?: boolean;
21
+ attributeKeys?: AttributeKeys;
22
+ }): Eligibility;
@@ -0,0 +1,20 @@
1
+ import { layerSlice } from '@delmaredigital/experiments-stats';
2
+ import { evaluateTargeting } from './targeting.js';
3
+ export function eligibility(experiment, unit, attributes, opts = {}) {
4
+ const unitId = experiment.unitType === 'visitor' ? unit.visitorId : unit.userId;
5
+ if (!unitId) return {
6
+ kind: experiment.unitType === 'user' ? 'deferred' : 'ineligible'
7
+ };
8
+ if ((opts.sessionUnknown ?? unit.sessionUnknown) && experiment.targeting.some((rule)=>rule.attribute === 'loggedIn' || rule.attribute === 'staff')) return {
9
+ kind: 'deferred'
10
+ };
11
+ if (!evaluateTargeting(experiment.targeting, attributes, opts.attributeKeys)) return {
12
+ kind: 'ineligible'
13
+ };
14
+ return {
15
+ kind: 'eligible',
16
+ unitId,
17
+ inLayer: !experiment.layer || experiment.layer.slices.includes(layerSlice(experiment.layer.slug, unitId)),
18
+ coverage: experiment.layer ? 1 : experiment.coverage
19
+ };
20
+ }