@delmaredigital/payload-experiments 0.2.0-rc.7 → 0.3.0-rc.4

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 (336) hide show
  1. package/README.md +202 -26
  2. package/dist/admin/PreviewLinks.d.ts +2 -1
  3. package/dist/admin/PreviewLinks.js +50 -21
  4. package/dist/admin/TestsNavLink.d.ts +3 -1
  5. package/dist/admin/TestsNavLink.js +2 -2
  6. package/dist/admin/actions/ConfirmDialog.d.ts +7 -4
  7. package/dist/admin/actions/ConfirmDialog.js +37 -82
  8. package/dist/admin/actions/DecideDialog.d.ts +3 -1
  9. package/dist/admin/actions/DecideDialog.js +30 -13
  10. package/dist/admin/actions/MakeContentResult.d.ts +13 -2
  11. package/dist/admin/actions/MakeContentResult.js +115 -16
  12. package/dist/admin/actions/actions.model.d.ts +2 -4
  13. package/dist/admin/actions/actions.model.js +2 -3
  14. package/dist/admin/allocation.model.js +3 -5
  15. package/dist/admin/api.d.ts +10 -4
  16. package/dist/admin/api.js +11 -7
  17. package/dist/admin/armSelect/ArmSelectField.js +14 -22
  18. package/dist/admin/arms/arms.model.d.ts +1 -21
  19. package/dist/admin/arms/arms.model.js +1 -153
  20. package/dist/admin/copy/actions.d.ts +39 -3
  21. package/dist/admin/copy/actions.js +40 -4
  22. package/dist/admin/copy/armSelect.js +5 -5
  23. package/dist/admin/copy/arms.d.ts +1 -6
  24. package/dist/admin/copy/arms.js +1 -16
  25. package/dist/admin/copy/audience.d.ts +2 -1
  26. package/dist/admin/copy/audience.js +3 -2
  27. package/dist/admin/copy/common.d.ts +13 -6
  28. package/dist/admin/copy/common.js +14 -8
  29. package/dist/admin/copy/goals.d.ts +21 -6
  30. package/dist/admin/copy/goals.js +27 -6
  31. package/dist/admin/copy/readiness.d.ts +22 -0
  32. package/dist/admin/copy/readiness.js +21 -0
  33. package/dist/admin/copy/recommendation.d.ts +0 -3
  34. package/dist/admin/copy/recommendation.js +0 -3
  35. package/dist/admin/copy/results.d.ts +43 -2
  36. package/dist/admin/copy/results.js +44 -3
  37. package/dist/admin/copy/review.d.ts +2 -2
  38. package/dist/admin/copy/review.js +2 -2
  39. package/dist/admin/copy/setting.d.ts +2 -0
  40. package/dist/admin/copy/setting.js +2 -0
  41. package/dist/admin/copy/surface.d.ts +7 -5
  42. package/dist/admin/copy/surface.js +9 -5
  43. package/dist/admin/copy/testPage.d.ts +10 -0
  44. package/dist/admin/copy/testPage.js +10 -0
  45. package/dist/admin/format/date.d.ts +2 -0
  46. package/dist/admin/format/date.js +10 -0
  47. package/dist/admin/paths.d.ts +2 -1
  48. package/dist/admin/paths.js +2 -1
  49. package/dist/admin/previewLinks.model.d.ts +4 -2
  50. package/dist/admin/previewLinks.model.js +8 -7
  51. package/dist/admin/results/CiChart.js +11 -21
  52. package/dist/admin/results/DataQualityPanel.d.ts +4 -2
  53. package/dist/admin/results/DataQualityPanel.js +30 -17
  54. package/dist/admin/results/DataTable.d.ts +16 -0
  55. package/dist/admin/results/DataTable.js +152 -0
  56. package/dist/admin/results/StatTile.d.ts +7 -0
  57. package/dist/admin/results/StatTile.js +22 -0
  58. package/dist/admin/results/VerdictHero.d.ts +13 -0
  59. package/dist/admin/results/{VerdictCard.js → VerdictHero.js} +15 -12
  60. package/dist/admin/results/legacy.d.ts +16 -0
  61. package/dist/admin/results/legacy.js +27 -0
  62. package/dist/admin/results/releasePages.model.d.ts +17 -0
  63. package/dist/admin/results/releasePages.model.js +10 -0
  64. package/dist/admin/results/results.css +64 -0
  65. package/dist/admin/results/results.model.d.ts +11 -7
  66. package/dist/admin/results/results.model.js +24 -7
  67. package/dist/admin/results/snapshot.d.ts +14 -0
  68. package/dist/admin/results/snapshot.js +3 -0
  69. package/dist/admin/surface/ResultsRoute.d.ts +1 -1
  70. package/dist/admin/surface/ResultsRoute.js +59 -34
  71. package/dist/admin/surface/SurfaceShell.d.ts +11 -2
  72. package/dist/admin/surface/SurfaceShell.js +49 -14
  73. package/dist/admin/surface/TestsHome.d.ts +6 -1
  74. package/dist/admin/surface/TestsHome.js +182 -114
  75. package/dist/admin/surface/TestsSurface.js +56 -16
  76. package/dist/admin/surface/goals/GoalForm.js +341 -308
  77. package/dist/admin/surface/goals/GoalsList.js +123 -89
  78. package/dist/admin/surface/goals/data.d.ts +8 -2
  79. package/dist/admin/surface/goals/data.js +24 -16
  80. package/dist/admin/surface/goals/goals.css +10 -13
  81. package/dist/admin/surface/home.model.d.ts +8 -8
  82. package/dist/admin/surface/home.model.js +23 -12
  83. package/dist/admin/surface/newTest/NewTest.js +9 -9
  84. package/dist/admin/surface/pageBlock/parts.js +31 -26
  85. package/dist/admin/surface/readiness/ActionDialogs.js +47 -19
  86. package/dist/admin/surface/readiness/Banner.d.ts +3 -0
  87. package/dist/admin/surface/readiness/Banner.js +58 -33
  88. package/dist/admin/surface/readiness/SaveStatus.d.ts +1 -0
  89. package/dist/admin/surface/readiness/SaveStatus.js +6 -3
  90. package/dist/admin/surface/readiness/readiness.model.d.ts +2 -0
  91. package/dist/admin/surface/readiness/readiness.model.js +25 -9
  92. package/dist/admin/surface/readiness/useTransition.d.ts +1 -0
  93. package/dist/admin/surface/readiness/useTransition.js +2 -1
  94. package/dist/admin/surface/results/ReleasePages.d.ts +14 -0
  95. package/dist/admin/surface/results/ReleasePages.js +116 -0
  96. package/dist/admin/surface/results/ResultsBody.d.ts +11 -1
  97. package/dist/admin/surface/results/ResultsBody.js +277 -105
  98. package/dist/admin/surface/results/ResultsHeader.d.ts +10 -0
  99. package/dist/admin/surface/results/ResultsHeader.js +177 -0
  100. package/dist/admin/surface/results/recommendation.model.d.ts +3 -2
  101. package/dist/admin/surface/results/recommendation.model.js +12 -9
  102. package/dist/admin/surface/results/useResults.d.ts +21 -3
  103. package/dist/admin/surface/results/useResults.js +2 -1
  104. package/dist/admin/surface/setting/parts.js +75 -88
  105. package/dist/admin/surface/setting/setting.css +3 -15
  106. package/dist/admin/surface/surface.css +61 -47
  107. package/dist/admin/surface/test/AdvancedSection.js +55 -48
  108. package/dist/admin/surface/test/GoalSection.js +46 -47
  109. package/dist/admin/surface/test/LengthSection.js +46 -34
  110. package/dist/admin/surface/test/Rail.d.ts +4 -0
  111. package/dist/admin/surface/test/Rail.js +83 -0
  112. package/dist/admin/surface/test/ReadinessBar.d.ts +5 -0
  113. package/dist/admin/surface/test/ReadinessBar.js +33 -0
  114. package/dist/admin/surface/test/Section.d.ts +1 -1
  115. package/dist/admin/surface/test/Section.js +36 -16
  116. package/dist/admin/surface/test/TargetingSection.js +168 -142
  117. package/dist/admin/surface/test/TestPage.d.ts +2 -1
  118. package/dist/admin/surface/test/TestPage.js +146 -68
  119. package/dist/admin/surface/test/VersionsSection.js +151 -120
  120. package/dist/admin/surface/test/fields.d.ts +11 -4
  121. package/dist/admin/surface/test/fields.js +84 -85
  122. package/dist/admin/surface/test/jumpToSection.d.ts +3 -0
  123. package/dist/admin/surface/test/jumpToSection.js +10 -0
  124. package/dist/admin/surface/test/pageParts.js +9 -3
  125. package/dist/admin/surface/test/testPage.css +81 -14
  126. package/dist/admin/surface/test/testPage.model.d.ts +2 -3
  127. package/dist/admin/surface/test/testPage.model.js +1 -21
  128. package/dist/admin/surface/useTestDocument.js +12 -5
  129. package/dist/admin/targeting/targeting.model.d.ts +1 -3
  130. package/dist/admin/targeting/targeting.model.js +3 -8
  131. package/dist/admin/theme.css +115 -36
  132. package/dist/admin/theme.d.ts +38 -0
  133. package/dist/admin/theme.js +38 -1
  134. package/dist/admin/useApi.d.ts +17 -2
  135. package/dist/admin/useLayerUsage.js +14 -23
  136. package/dist/admin/views/ExperimentsRootView.js +12 -2
  137. package/dist/admin/views/TestsNavLinkServer.d.ts +2 -0
  138. package/dist/admin/views/TestsNavLinkServer.js +43 -0
  139. package/dist/arms/copy.d.ts +6 -0
  140. package/dist/arms/copy.js +16 -0
  141. package/dist/arms/model.d.ts +23 -0
  142. package/dist/arms/model.js +173 -0
  143. package/dist/collections/experiments/config.js +25 -0
  144. package/dist/collections/experiments/lockHook.js +3 -1
  145. package/dist/collections/identities.js +6 -1
  146. package/dist/collections/metrics.js +7 -0
  147. package/dist/collections/metricsGuards.d.ts +9 -0
  148. package/dist/collections/metricsGuards.js +33 -0
  149. package/dist/contentDone.d.ts +4 -0
  150. package/dist/contentDone.js +5 -0
  151. package/dist/db/appends.d.ts +6 -2
  152. package/dist/db/appends.js +10 -4
  153. package/dist/db/identities.d.ts +8 -7
  154. package/dist/db/identities.js +9 -6
  155. package/dist/db/locks.d.ts +6 -0
  156. package/dist/db/locks.js +13 -1
  157. package/dist/db/metricUsage.d.ts +19 -0
  158. package/dist/db/metricUsage.js +54 -0
  159. package/dist/db/names.d.ts +5 -0
  160. package/dist/db/names.js +5 -0
  161. package/dist/db/pageOccupancy.d.ts +12 -0
  162. package/dist/db/pageOccupancy.js +45 -0
  163. package/dist/db/publishedContent.d.ts +10 -0
  164. package/dist/db/publishedContent.js +43 -0
  165. package/dist/db/results.d.ts +2 -0
  166. package/dist/db/results.js +6 -2
  167. package/dist/db/snapshots.d.ts +18 -6
  168. package/dist/db/snapshots.js +25 -8
  169. package/dist/db/sweep.js +11 -6
  170. package/dist/endpoints/index.js +19 -1
  171. package/dist/endpoints/results.js +5 -0
  172. package/dist/endpoints/sanitizeReadinessDetail.d.ts +1 -0
  173. package/dist/endpoints/sanitizeReadinessDetail.js +2 -0
  174. package/dist/endpoints/summary.js +20 -2
  175. package/dist/endpoints/transitions.d.ts +15 -0
  176. package/dist/endpoints/transitions.js +36 -0
  177. package/dist/errors.d.ts +8 -0
  178. package/dist/errors.js +28 -0
  179. package/dist/exports/adminRsc.d.ts +1 -0
  180. package/dist/exports/adminRsc.js +1 -0
  181. package/dist/exports/index.d.ts +2 -1
  182. package/dist/exports/index.js +1 -0
  183. package/dist/exports/puck.d.ts +2 -1
  184. package/dist/exports/puck.js +1 -1
  185. package/dist/exports/puckEditor.d.ts +4 -1
  186. package/dist/exports/puckEditor.js +3 -1
  187. package/dist/exports/rsc.d.ts +6 -1
  188. package/dist/exports/rsc.js +2 -0
  189. package/dist/hooks/pageGuard.d.ts +5 -0
  190. package/dist/hooks/pageGuard.js +139 -0
  191. package/dist/lib/fetchAllDocs.d.ts +16 -0
  192. package/dist/lib/fetchAllDocs.js +56 -0
  193. package/dist/log.js +6 -3
  194. package/dist/manifest/compile.d.ts +5 -0
  195. package/dist/manifest/compile.js +5 -1
  196. package/dist/options.js +16 -0
  197. package/dist/plugin.js +30 -2
  198. package/dist/puck/ExperimentPicker.js +47 -16
  199. package/dist/puck/ExperimentSlot.d.ts +51 -1
  200. package/dist/puck/ExperimentSlot.js +23 -12
  201. package/dist/puck/copy.d.ts +6 -5
  202. package/dist/puck/copy.js +6 -5
  203. package/dist/puck/editor/CreateExperimentDrawer.js +46 -56
  204. package/dist/puck/editor/ExperimentSlotEditor.d.ts +52 -1
  205. package/dist/puck/editor/ExperimentSlotEditor.js +292 -137
  206. package/dist/puck/editor/ResultsReadout.d.ts +6 -0
  207. package/dist/puck/editor/ResultsReadout.js +99 -0
  208. package/dist/puck/editor/VersionBuilder.d.ts +34 -0
  209. package/dist/puck/editor/VersionBuilder.js +317 -0
  210. package/dist/puck/editor/armWrites.d.ts +7 -0
  211. package/dist/puck/editor/armWrites.js +71 -0
  212. package/dist/puck/editor/armsCache.d.ts +14 -0
  213. package/dist/puck/editor/armsCache.js +78 -0
  214. package/dist/puck/editor/copy.d.ts +33 -1
  215. package/dist/puck/editor/copy.js +45 -8
  216. package/dist/puck/editor/createTest.d.ts +1 -1
  217. package/dist/puck/editor/createTest.js +25 -14
  218. package/dist/puck/editor/editor.chrome.css +121 -0
  219. package/dist/puck/editor/editor.css +288 -204
  220. package/dist/puck/editor/editor.model.d.ts +4 -1
  221. package/dist/puck/editor/editor.model.js +10 -7
  222. package/dist/puck/editor/experimentOutlinePlugin.d.ts +3 -0
  223. package/dist/puck/editor/experimentOutlinePlugin.js +89 -0
  224. package/dist/puck/experimentSlotField.d.ts +2 -0
  225. package/dist/puck/experimentSlotField.js +11 -3
  226. package/dist/puck/hostContext.d.ts +10 -0
  227. package/dist/puck/hostContext.js +12 -0
  228. package/dist/puck/routes.d.ts +12 -0
  229. package/dist/puck/routes.js +11 -0
  230. package/dist/puck/slotConfig.d.ts +1 -1
  231. package/dist/puck/slotConfig.js +2 -2
  232. package/dist/results/aggregate.js +4 -3
  233. package/dist/results/analyse.d.ts +4 -1
  234. package/dist/results/analyse.js +27 -11
  235. package/dist/results/cuped.js +3 -2
  236. package/dist/results/identityCredit.d.ts +15 -7
  237. package/dist/results/identityCredit.js +32 -33
  238. package/dist/results/rolloutUnits.js +2 -1
  239. package/dist/results/snapshot.js +8 -2
  240. package/dist/results/srm.js +2 -1
  241. package/dist/sanitizeReadinessDetail.d.ts +7 -0
  242. package/dist/sanitizeReadinessDetail.js +63 -0
  243. package/dist/server/linkIdentity.d.ts +10 -1
  244. package/dist/server/linkIdentity.js +17 -15
  245. package/dist/server/metadata.d.ts +5 -0
  246. package/dist/server/metadata.js +18 -3
  247. package/dist/server/normaliseContentRelationships.d.ts +7 -0
  248. package/dist/server/normaliseContentRelationships.js +62 -0
  249. package/dist/server/recordContentChange.d.ts +1 -1
  250. package/dist/server/recordContentChange.js +17 -8
  251. package/dist/server/runResults.d.ts +3 -2
  252. package/dist/server/runResults.js +157 -35
  253. package/dist/targeting/values.d.ts +4 -0
  254. package/dist/targeting/values.js +10 -0
  255. package/dist/transitions/addMetric.js +8 -0
  256. package/dist/transitions/archive.js +2 -3
  257. package/dist/transitions/checkStart.d.ts +10 -3
  258. package/dist/transitions/checkStart.js +25 -0
  259. package/dist/transitions/contentFields.d.ts +7 -0
  260. package/dist/transitions/contentFields.js +20 -0
  261. package/dist/transitions/decide.js +22 -2
  262. package/dist/transitions/dismissContentEntry.d.ts +8 -0
  263. package/dist/transitions/dismissContentEntry.js +45 -0
  264. package/dist/transitions/makeContent.d.ts +5 -2
  265. package/dist/transitions/makeContent.js +217 -118
  266. package/dist/transitions/phaseRecord.d.ts +8 -2
  267. package/dist/transitions/placements.d.ts +3 -6
  268. package/dist/transitions/placements.js +12 -1
  269. package/dist/transitions/preflightData.d.ts +9 -1
  270. package/dist/transitions/preflightData.js +138 -20
  271. package/dist/transitions/releasePages.d.ts +8 -0
  272. package/dist/transitions/releasePages.js +26 -0
  273. package/dist/transitions/restoreContentEntry.d.ts +8 -0
  274. package/dist/transitions/restoreContentEntry.js +47 -0
  275. package/dist/transitions/start.js +62 -7
  276. package/dist/types/contracts.d.ts +24 -4
  277. package/dist/types/endpoints.d.ts +34 -3
  278. package/dist/types/index.d.ts +1 -0
  279. package/dist/types/index.js +1 -0
  280. package/dist/types/reasons.d.ts +3 -0
  281. package/dist/types/reasons.js +1 -0
  282. package/dist/types/snapshot.d.ts +6 -1
  283. package/dist/ui/Banner.d.ts +2 -0
  284. package/dist/ui/Banner.js +2 -0
  285. package/dist/ui/Breadcrumb.d.ts +7 -0
  286. package/dist/ui/Breadcrumb.js +25 -0
  287. package/dist/ui/Button.d.ts +15 -0
  288. package/dist/ui/Button.js +32 -0
  289. package/dist/ui/Card.d.ts +12 -0
  290. package/dist/ui/Card.js +40 -0
  291. package/dist/ui/Checkbox.d.ts +2 -0
  292. package/dist/ui/Checkbox.js +2 -0
  293. package/dist/{admin/components → ui}/ChipsInput.d.ts +4 -3
  294. package/dist/{admin/components → ui}/ChipsInput.js +20 -12
  295. package/dist/ui/Dialog.d.ts +13 -0
  296. package/dist/ui/Dialog.js +85 -0
  297. package/dist/ui/EmptyState.d.ts +2 -0
  298. package/dist/ui/EmptyState.js +2 -0
  299. package/dist/ui/Feedback.d.ts +24 -0
  300. package/dist/ui/Feedback.js +40 -0
  301. package/dist/ui/Inputs.d.ts +34 -0
  302. package/dist/ui/Inputs.js +225 -0
  303. package/dist/ui/NumberInput.d.ts +2 -0
  304. package/dist/ui/NumberInput.js +2 -0
  305. package/dist/ui/Range.d.ts +2 -0
  306. package/dist/ui/Range.js +2 -0
  307. package/dist/ui/SearchInput.d.ts +2 -0
  308. package/dist/ui/SearchInput.js +2 -0
  309. package/dist/ui/SectionCard.d.ts +2 -0
  310. package/dist/ui/SectionCard.js +2 -0
  311. package/dist/ui/Select.d.ts +2 -0
  312. package/dist/ui/Select.js +2 -0
  313. package/dist/ui/StatusChip.d.ts +2 -0
  314. package/dist/ui/StatusChip.js +2 -0
  315. package/dist/ui/Tabs.d.ts +16 -0
  316. package/dist/ui/Tabs.js +61 -0
  317. package/dist/ui/TextInput.d.ts +2 -0
  318. package/dist/ui/TextInput.js +2 -0
  319. package/dist/ui/Textarea.d.ts +2 -0
  320. package/dist/ui/Textarea.js +2 -0
  321. package/dist/ui/Toggle.d.ts +2 -0
  322. package/dist/ui/Toggle.js +2 -0
  323. package/dist/ui/accent.d.ts +40 -0
  324. package/dist/ui/accent.js +139 -0
  325. package/dist/ui/index.d.ts +9 -0
  326. package/dist/ui/index.js +9 -0
  327. package/dist/ui/ui.css +92 -0
  328. package/package.json +5 -2
  329. package/dist/admin/components/chipsInput.css +0 -11
  330. package/dist/admin/results/ArmTable.d.ts +0 -8
  331. package/dist/admin/results/ArmTable.js +0 -113
  332. package/dist/admin/results/VerdictCard.d.ts +0 -9
  333. package/dist/admin/surface/goals/GoalsBreadcrumb.d.ts +0 -5
  334. package/dist/admin/surface/goals/GoalsBreadcrumb.js +0 -47
  335. package/dist/admin/surface/test/TestHeader.d.ts +0 -4
  336. package/dist/admin/surface/test/TestHeader.js +0 -70
@@ -1,7 +1,7 @@
1
1
  import type { Arm, DocId, ExperimentKind, ExposurePolicy, FlagPoint, MetricSource, ResolvedOptions, Status, TargetingRule, UnitType } from '../types/contracts.js';
2
2
  import type { ArmIssueCode, PreflightResult } from '../types/endpoints.js';
3
3
  import type { BlockPlacement, FlagPlacement } from './placements.js';
4
- export type ReadinessCode = ArmIssueCode | 'invalid-status' | 'no-planned-duration' | 'no-primary-metric' | 'no-reach-per-day' | 'page-no-targets' | 'page-user-unit' | 'flag-no-point' | 'flag-params-invalid' | 'flag-point-busy' | 'flag-placement-missing' | 'no-placements' | 'empty-slot' | 'layer-unit-mismatch' | 'layer-full' | 'unknown-metric-source' | 'unit-source-mismatch' | 'undeclared-targeting-key' | 'invalid-targeting-rule';
4
+ export type ReadinessCode = ArmIssueCode | 'invalid-status' | 'no-planned-duration' | 'no-primary-metric' | 'no-reach-per-day' | 'page-no-targets' | 'page-in-use' | 'page-target-unpublished' | 'page-targets-unchecked' | 'version-page-unavailable' | 'version-page-unchecked' | 'page-user-unit' | 'flag-no-point' | 'flag-params-invalid' | 'flag-point-busy' | 'flag-placement-missing' | 'no-placements' | 'empty-slot' | 'make-content-unsupported' | 'layer-unit-mismatch' | 'layer-full' | 'unknown-metric-source' | 'unit-source-mismatch' | 'block-path-rule' | 'undeclared-targeting-key' | 'invalid-targeting-rule';
5
5
  export interface StartExperiment {
6
6
  id: DocId;
7
7
  slug: string;
@@ -38,6 +38,7 @@ export interface StartMetric {
38
38
  id: DocId;
39
39
  source: string;
40
40
  }
41
+ export type PlacementFailure = 'placements-failed' | 'forbidden' | 'not-found';
41
42
  export interface CheckStartInput {
42
43
  experiment: StartExperiment;
43
44
  metrics: StartMetric[];
@@ -45,9 +46,12 @@ export interface CheckStartInput {
45
46
  flagPoints?: FlagPoint[];
46
47
  placements?: BlockPlacement[];
47
48
  flagPlacements?: FlagPlacement[];
48
- placementError?: string;
49
- flagPlacementError?: string;
49
+ placementError?: PlacementFailure;
50
+ flagPlacementError?: PlacementFailure;
50
51
  flagPointBusy?: boolean;
52
+ unpublishedPageTargets?: string[];
53
+ pageTargetsUnchecked?: boolean;
54
+ pageTestsInUse?: Pick<StartExperiment, 'id' | 'arms'>[];
51
55
  layer?: {
52
56
  unitType: UnitType;
53
57
  usedSlices: number[];
@@ -58,5 +62,8 @@ export declare const ARM_CHECK_ORDER: readonly ArmIssueCode[];
58
62
  export declare const relationshipId: (value: DocId | {
59
63
  id: DocId;
60
64
  } | null | undefined) => DocId | undefined;
65
+ /** Every path the runtime would match for these arms (pageRewrite, experimentMetadata): any string
66
+ * pageTarget, holdout included, normalised the same way ('' is '/'). Occupancy and lock keys use this. */
67
+ export declare const pageTargetPaths: (arms: readonly Arm[]) => string[];
61
68
  /** Pure Review/Start predicates in refusal order. Inapplicable checks pass. */
62
69
  export declare function checkStart(input: CheckStartInput): PreflightResult['checks'];
@@ -1,4 +1,5 @@
1
1
  import { checkArms } from '../collections/experiments/armInvariants.js';
2
+ import { normalisePath } from '../runtime/normalisePath.js';
2
3
  import { isOpValid } from '../targeting/ops.js';
3
4
  export const ARM_CHECK_ORDER = [
4
5
  'weights-total',
@@ -13,6 +14,10 @@ export const ARM_CHECK_ORDER = [
13
14
  'duplicate-slot'
14
15
  ];
15
16
  export const relationshipId = (value)=>value == null ? undefined : typeof value === 'object' ? value.id : value;
17
+ /** Every path the runtime would match for these arms (pageRewrite, experimentMetadata): any string
18
+ * pageTarget, holdout included, normalised the same way ('' is '/'). Occupancy and lock keys use this. */ export const pageTargetPaths = (arms)=>[
19
+ ...new Set(arms.filter((arm)=>typeof arm.pageTarget === 'string').map((arm)=>normalisePath(arm.pageTarget)))
20
+ ].sort();
16
21
  const positive = (value)=>typeof value === 'number' && Number.isFinite(value) && value > 0;
17
22
  function invalidParams(arms, point) {
18
23
  return arms.flatMap((arm)=>{
@@ -56,6 +61,14 @@ function invalidParams(arms, point) {
56
61
  'viewport'
57
62
  ].includes(e.exposurePolicy) || positive(e.analysis?.reachPerDay));
58
63
  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));
64
+ const occupiedPaths = new Set((input.pageTestsInUse ?? []).filter((other)=>String(other.id) !== String(e.id)).flatMap((other)=>pageTargetPaths(other.arms)));
65
+ const sharedPaths = e.kind === 'page' ? pageTargetPaths(e.arms).filter((path)=>occupiedPaths.has(path)) : [];
66
+ add('page-in-use', sharedPaths.length === 0, {
67
+ paths: sharedPaths
68
+ });
69
+ add('page-target-unpublished', !input.unpublishedPageTargets?.length, input.unpublishedPageTargets?.[0]);
70
+ add('page-targets-unchecked', true);
71
+ if (input.pageTargetsUnchecked) checks[checks.length - 1].warning = true;
59
72
  add('page-user-unit', e.kind !== 'page' || e.unitType === 'visitor');
60
73
  const point = input.flagPoints?.find((point)=>point.id === e.flagPoint);
61
74
  add('flag-no-point', e.kind !== 'flag' || !!point);
@@ -72,6 +85,12 @@ function invalidParams(arms, point) {
72
85
  add('no-placements', e.kind !== 'block' || !input.placementError && placements.length > 0, input.placementError);
73
86
  const empty = placements.filter((placement)=>e.arms.some((arm)=>arm.kind !== 'holdout' && (!arm.slot || !(placement.slots[arm.slot] > 0))));
74
87
  add('empty-slot', e.kind !== 'block' || empty.length === 0, empty);
88
+ add('make-content-unsupported', true);
89
+ const localized = placements.find((placement)=>placement.localized);
90
+ if (localized) Object.assign(checks[checks.length - 1], {
91
+ warning: true,
92
+ detail: localized.title
93
+ });
75
94
  add('layer-unit-mismatch', e.layer == null || input.layer?.unitType === e.unitType);
76
95
  const free = 100 - new Set(input.layer?.usedSlices).size;
77
96
  add('layer-full', e.layer == null || !!input.layer && free >= e.allocation, {
@@ -82,6 +101,12 @@ function invalidParams(arms, point) {
82
101
  add('unknown-metric-source', unknown.length === 0, unknown);
83
102
  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
103
  add('unit-source-mismatch', mismatches.length === 0, mismatches);
104
+ const pathRules = (e.targeting ?? []).flatMap((rule, index)=>rule.attribute === 'path' ? [
105
+ index
106
+ ] : []);
107
+ add('block-path-rule', e.kind !== 'block' || pathRules.length === 0, {
108
+ rules: pathRules
109
+ });
85
110
  const undeclared = (e.targeting ?? []).filter((rule)=>!Object.hasOwn(input.attributeKeys ?? {}, rule.attribute));
86
111
  add('undeclared-targeting-key', undeclared.length === 0, {
87
112
  message: 'Targeting uses an undeclared attribute.',
@@ -0,0 +1,7 @@
1
+ import type { Block, Field, SanitizedConfig } from 'payload';
2
+ /** Payload keeps both inline definitions and slug references in blockReferences. */
3
+ export declare function contentBlocks(field: Extract<Field, {
4
+ type: 'blocks';
5
+ }>, blocks?: Block[]): Block[];
6
+ /** Any localized data makes a full-document publish unsafe in Payload 3.90. */
7
+ export declare function hasLocalizedFields(fields: Field[], config: Pick<SanitizedConfig, 'localization' | 'blocks'>, seen?: Set<Field>): boolean;
@@ -0,0 +1,20 @@
1
+ /** Payload keeps both inline definitions and slug references in blockReferences. */ export function contentBlocks(field, blocks = []) {
2
+ return [
3
+ ...field.blocks,
4
+ ...(field.blockReferences ?? []).flatMap((reference)=>typeof reference === 'string' ? blocks.filter((block)=>block.slug === reference) : [
5
+ reference
6
+ ])
7
+ ];
8
+ }
9
+ /** Any localized data makes a full-document publish unsafe in Payload 3.90. */ export function hasLocalizedFields(fields, config, seen = new Set()) {
10
+ if (!config.localization) return false;
11
+ const blocks = config.blocks ?? [];
12
+ return fields.some((field)=>{
13
+ if (seen.has(field)) return false;
14
+ const branch = new Set(seen).add(field);
15
+ if ('localized' in field && field.localized) return true;
16
+ if ('tabs' in field && field.tabs.some((tab)=>tab.localized || hasLocalizedFields(tab.fields, config, branch))) return true;
17
+ if ('fields' in field && hasLocalizedFields(field.fields, config, branch)) return true;
18
+ return field.type === 'blocks' && contentBlocks(field, blocks).some((block)=>hasLocalizedFields(block.fields, config, branch));
19
+ });
20
+ }
@@ -1,6 +1,9 @@
1
+ import { DEFAULT_SEQUENTIAL_TUNING_PARAMETER } from '../constants.js';
1
2
  import { reclaimSlices } from '../db/slices.js';
2
3
  import { decideGuard } from '../results/decideGuard.js';
3
- import { relationshipId } from './checkStart.js';
4
+ import { lockPagePaths } from '../db/locks.js';
5
+ import { routedPageTests } from '../db/pageOccupancy.js';
6
+ import { pageTargetPaths, relationshipId } from './checkStart.js';
4
7
  import { expectedConfirm, matchesConfirm, storedWinnerLabel } from './confirmPhrases.js';
5
8
  import { withTransition } from './kernel.js';
6
9
  import { readPhaseRecord, setPhaseEndedAt, writePhaseRecord } from './phaseRecord.js';
@@ -44,6 +47,19 @@ export async function decide(req, { experimentId, winnerArm, note, confirmation
44
47
  }
45
48
  const test = await readPhaseRecord(req, experimentId, current.phase);
46
49
  if (test.phaseKind !== 'test') refuse('invalid-status');
50
+ if (current.kind === 'page' && current.status === 'stopped') {
51
+ // Deciding a stopped page test restores its routing: recheck page occupancy under the
52
+ // sorted path locks Start takes, so another test that started on these pages meanwhile wins.
53
+ // The frozen test-round arms are what decide restores; the stopped document's arms may have been edited.
54
+ const mine = pageTargetPaths(test.arms);
55
+ await lockPagePaths(exec, mine);
56
+ // Other tests' routed (frozen-round) arms, as the runtime matches them.
57
+ const occupied = new Set((await routedPageTests(exec)).filter((other)=>String(other.id) !== String(experimentId)).flatMap((other)=>pageTargetPaths(other.arms)));
58
+ const shared = mine.filter((path)=>occupied.has(path));
59
+ if (shared.length) refuse('page-in-use', {
60
+ paths: shared
61
+ });
62
+ }
47
63
  const winner = test.arms.find((arm)=>arm.key === winnerArm && arm.kind !== 'holdout');
48
64
  if (!winner) refuse('invalid-winner');
49
65
  // §7.8: decision snapshot lookup serves the authorised transition.
@@ -112,7 +128,11 @@ export async function decide(req, { experimentId, winnerArm, note, confirmation
112
128
  allocationChanges: [],
113
129
  targeting: test.targeting,
114
130
  metricDefinitions: test.metricDefinitions,
115
- analysis: test.analysis,
131
+ analysis: {
132
+ ...test.analysis,
133
+ // Older test phases may predate freezing the effective tuning value.
134
+ sequentialTuningParameter: test.analysis?.sequentialTuningParameter ?? DEFAULT_SEQUENTIAL_TUNING_PARAMETER
135
+ },
116
136
  contentChanges: []
117
137
  });
118
138
  }
@@ -0,0 +1,8 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { DocId } from '../types/contracts.js';
3
+ import type { DismissContentEntryBody } from '../types/endpoints.js';
4
+ export declare function dismissContentEntry(req: PayloadRequest, { experimentId, collection, id }: DismissContentEntryBody & {
5
+ experimentId: DocId;
6
+ }): Promise<{
7
+ doc: import("payload").JsonObject & import("payload").TypeWithID;
8
+ }>;
@@ -0,0 +1,45 @@
1
+ import { relationshipId } from './checkStart.js';
2
+ import { withTransition } from './kernel.js';
3
+ import { readExperiment, refuse, updateExperiment } from './start.js';
4
+ export async function dismissContentEntry(req, { experimentId, collection, id }) {
5
+ const before = await readExperiment(req, experimentId);
6
+ // Only the audit report changes; assignment and the manifest stay the same.
7
+ const { result: doc } = await withTransition(req, {
8
+ experimentId,
9
+ layerId: relationshipId(before.layer),
10
+ flagPointId: before.kind === 'flag' ? before.flagPoint : undefined,
11
+ bump: false
12
+ }, async ({ req })=>{
13
+ const current = await readExperiment(req, experimentId);
14
+ const decision = current.decision;
15
+ const report = decision?.contentReport;
16
+ const index = Array.isArray(report?.docs) ? report.docs.findIndex((entry)=>entry.collection === collection && String(entry.id) === String(id)) : -1;
17
+ const entry = report?.docs?.[index];
18
+ if (current.status !== 'decided' || !decision?.winnerArm || decision.discardedAt) refuse('not-decided');
19
+ if (!entry) refuse('entry-not-found');
20
+ if (entry.ok !== false) refuse('entry-ok');
21
+ if (entry.dismissedAt) refuse('entry-dismissed');
22
+ const docs = report.docs.map((entry, i)=>i === index ? {
23
+ ...entry,
24
+ dismissedAt: new Date().toISOString(),
25
+ dismissedBy: req.user?.id == null ? null : String(req.user.id)
26
+ } : entry);
27
+ return updateExperiment(req, experimentId, {
28
+ decision: {
29
+ ...decision,
30
+ contentReport: {
31
+ ...report,
32
+ docs
33
+ }
34
+ }
35
+ });
36
+ });
37
+ req.payload.logger.info({
38
+ experimentId,
39
+ collection,
40
+ id
41
+ }, 'Experiment content entry dismissed');
42
+ return {
43
+ doc
44
+ };
45
+ }
@@ -1,11 +1,14 @@
1
1
  import { type PayloadRequest } from 'payload';
2
+ import type { ContentReportReason } from '../types/reasons.js';
2
3
  import type { DocId, ResolvedOptions } from '../types/contracts.js';
3
4
  type ContentDoc = {
4
5
  collection: string;
5
6
  id: DocId;
6
7
  ok: boolean;
7
- detail?: string;
8
- error?: string;
8
+ detail?: ContentReportReason;
9
+ error?: ContentReportReason;
10
+ dismissedAt?: string;
11
+ dismissedBy?: string | null;
9
12
  };
10
13
  export declare function makeContent<TUser extends {
11
14
  id: DocId;