@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.
- package/LICENSE +21 -0
- package/README.md +582 -2
- package/dist/access.d.ts +1 -0
- package/dist/access.js +2 -0
- package/dist/admin/AllocationField.d.ts +2 -0
- package/dist/admin/AllocationField.js +80 -0
- package/dist/admin/FlagPointSelect.d.ts +2 -0
- package/dist/admin/FlagPointSelect.js +54 -0
- package/dist/admin/LayerUsage.d.ts +2 -0
- package/dist/admin/LayerUsage.js +24 -0
- package/dist/admin/LockedFieldNotice.d.ts +2 -0
- package/dist/admin/LockedFieldNotice.js +26 -0
- package/dist/admin/MetricSourceSelect.d.ts +2 -0
- package/dist/admin/MetricSourceSelect.js +32 -0
- package/dist/admin/PhaseHistory.d.ts +2 -0
- package/dist/admin/PhaseHistory.js +76 -0
- package/dist/admin/PreviewLinks.d.ts +11 -0
- package/dist/admin/PreviewLinks.js +69 -0
- package/dist/admin/StatusCell.d.ts +3 -0
- package/dist/admin/StatusCell.js +19 -0
- package/dist/admin/SummaryCell.d.ts +2 -0
- package/dist/admin/SummaryCell.js +32 -0
- package/dist/admin/actions/AddMetricDialog.d.ts +6 -0
- package/dist/admin/actions/AddMetricDialog.js +138 -0
- package/dist/admin/actions/AllocateDialog.d.ts +6 -0
- package/dist/admin/actions/AllocateDialog.js +81 -0
- package/dist/admin/actions/ConfirmDialog.d.ts +14 -0
- package/dist/admin/actions/ConfirmDialog.js +192 -0
- package/dist/admin/actions/DecideDialog.d.ts +7 -0
- package/dist/admin/actions/DecideDialog.js +173 -0
- package/dist/admin/actions/ExperimentActions.d.ts +3 -0
- package/dist/admin/actions/ExperimentActions.js +110 -0
- package/dist/admin/actions/MakeContentResult.d.ts +6 -0
- package/dist/admin/actions/MakeContentResult.js +50 -0
- package/dist/admin/actions/actions.model.d.ts +37 -0
- package/dist/admin/actions/actions.model.js +71 -0
- package/dist/admin/actions/decide.model.d.ts +21 -0
- package/dist/admin/actions/decide.model.js +38 -0
- package/dist/admin/allocation.model.d.ts +20 -0
- package/dist/admin/allocation.model.js +38 -0
- package/dist/admin/api.d.ts +32 -0
- package/dist/admin/api.js +95 -0
- package/dist/admin/armSelect/ArmSelectField.d.ts +2 -0
- package/dist/admin/armSelect/ArmSelectField.js +185 -0
- package/dist/admin/armSelect/armSelect.model.d.ts +30 -0
- package/dist/admin/armSelect/armSelect.model.js +20 -0
- package/dist/admin/arms/ArmsField.d.ts +2 -0
- package/dist/admin/arms/ArmsField.js +310 -0
- package/dist/admin/arms/ParamsEditor.d.ts +16 -0
- package/dist/admin/arms/ParamsEditor.js +79 -0
- package/dist/admin/arms/arms.model.d.ts +21 -0
- package/dist/admin/arms/arms.model.js +153 -0
- package/dist/admin/arms/pageTarget.model.d.ts +4 -0
- package/dist/admin/arms/pageTarget.model.js +19 -0
- package/dist/admin/arms/params.model.d.ts +7 -0
- package/dist/admin/arms/params.model.js +35 -0
- package/dist/admin/copy/actions.d.ts +38 -0
- package/dist/admin/copy/actions.js +38 -0
- package/dist/admin/copy/armSelect.d.ts +11 -0
- package/dist/admin/copy/armSelect.js +11 -0
- package/dist/admin/copy/arms.d.ts +37 -0
- package/dist/admin/copy/arms.js +47 -0
- package/dist/admin/copy/common.d.ts +20 -0
- package/dist/admin/copy/common.js +27 -0
- package/dist/admin/copy/history.d.ts +9 -0
- package/dist/admin/copy/history.js +9 -0
- package/dist/admin/copy/metrics.d.ts +9 -0
- package/dist/admin/copy/metrics.js +9 -0
- package/dist/admin/copy/results.d.ts +78 -0
- package/dist/admin/copy/results.js +81 -0
- package/dist/admin/copy/review.d.ts +33 -0
- package/dist/admin/copy/review.js +127 -0
- package/dist/admin/copy/status.d.ts +8 -0
- package/dist/admin/copy/status.js +8 -0
- package/dist/admin/copy/tabs.d.ts +7 -0
- package/dist/admin/copy/tabs.js +7 -0
- package/dist/admin/copy/who.d.ts +36 -0
- package/dist/admin/copy/who.js +36 -0
- package/dist/admin/labels.d.ts +45 -0
- package/dist/admin/labels.js +53 -0
- package/dist/admin/layerUsage.model.d.ts +15 -0
- package/dist/admin/layerUsage.model.js +18 -0
- package/dist/admin/lockedNotice.model.d.ts +3 -0
- package/dist/admin/lockedNotice.model.js +18 -0
- package/dist/admin/metricSource.model.d.ts +7 -0
- package/dist/admin/metricSource.model.js +11 -0
- package/dist/admin/paths.d.ts +16 -0
- package/dist/admin/paths.js +16 -0
- package/dist/admin/permissions.model.d.ts +10 -0
- package/dist/admin/permissions.model.js +4 -0
- package/dist/admin/phaseHistory.model.d.ts +19 -0
- package/dist/admin/phaseHistory.model.js +19 -0
- package/dist/admin/reload.d.ts +1 -0
- package/dist/admin/reload.js +1 -0
- package/dist/admin/results/ArmTable.d.ts +8 -0
- package/dist/admin/results/ArmTable.js +112 -0
- package/dist/admin/results/CappedCell.d.ts +4 -0
- package/dist/admin/results/CappedCell.js +24 -0
- package/dist/admin/results/CiChart.d.ts +10 -0
- package/dist/admin/results/CiChart.js +178 -0
- package/dist/admin/results/DataQualityPanel.d.ts +4 -0
- package/dist/admin/results/DataQualityPanel.js +40 -0
- package/dist/admin/results/ResultsView.d.ts +3 -0
- package/dist/admin/results/ResultsView.js +312 -0
- package/dist/admin/results/VerdictCard.d.ts +9 -0
- package/dist/admin/results/VerdictCard.js +49 -0
- package/dist/admin/results/chart.model.d.ts +32 -0
- package/dist/admin/results/chart.model.js +52 -0
- package/dist/admin/results/format.d.ts +10 -0
- package/dist/admin/results/format.js +55 -0
- package/dist/admin/results/results.model.d.ts +105 -0
- package/dist/admin/results/results.model.js +175 -0
- package/dist/admin/review/ReviewPanel.d.ts +2 -0
- package/dist/admin/review/ReviewPanel.js +268 -0
- package/dist/admin/review/review.model.d.ts +31 -0
- package/dist/admin/review/review.model.js +40 -0
- package/dist/admin/status.model.d.ts +16 -0
- package/dist/admin/status.model.js +17 -0
- package/dist/admin/styles.css +10 -0
- package/dist/admin/styles.d.ts +1 -0
- package/dist/admin/styles.js +2 -0
- package/dist/admin/summaryBatcher.d.ts +8 -0
- package/dist/admin/summaryBatcher.js +33 -0
- package/dist/admin/targeting/TargetingAttributeField.d.ts +2 -0
- package/dist/admin/targeting/TargetingAttributeField.js +31 -0
- package/dist/admin/targeting/TargetingOpField.d.ts +2 -0
- package/dist/admin/targeting/TargetingOpField.js +45 -0
- package/dist/admin/targeting/TargetingValueField.d.ts +2 -0
- package/dist/admin/targeting/TargetingValueField.js +142 -0
- package/dist/admin/targeting/targeting.model.d.ts +18 -0
- package/dist/admin/targeting/targeting.model.js +48 -0
- package/dist/admin/useApi.d.ts +20 -0
- package/dist/admin/useApi.js +10 -0
- package/dist/admin/useLayerUsage.d.ts +15 -0
- package/dist/admin/useLayerUsage.js +92 -0
- package/dist/client/ExperimentPageview.d.ts +1 -0
- package/dist/client/ExperimentPageview.js +27 -0
- package/dist/client/ExperimentsProvider.d.ts +6 -0
- package/dist/client/ExperimentsProvider.js +22 -0
- package/dist/client/ViewportExposure.d.ts +9 -0
- package/dist/client/ViewportExposure.js +69 -0
- package/dist/client/beacon.d.ts +17 -0
- package/dist/client/beacon.js +71 -0
- package/dist/client/context.d.ts +2 -0
- package/dist/client/context.js +2 -0
- package/dist/client/idempotency.d.ts +2 -0
- package/dist/client/idempotency.js +13 -0
- package/dist/client/trackConversionBeacon.d.ts +3 -0
- package/dist/client/trackConversionBeacon.js +16 -0
- package/dist/client/useExperiment.d.ts +5 -0
- package/dist/client/useExperiment.js +27 -0
- package/dist/client/useFlagPoint.d.ts +9 -0
- package/dist/client/useFlagPoint.js +32 -0
- package/dist/collections/access.d.ts +21 -0
- package/dist/collections/access.js +25 -0
- package/dist/collections/events.d.ts +3 -0
- package/dist/collections/events.js +117 -0
- package/dist/collections/experiments/armInvariants.d.ts +12 -0
- package/dist/collections/experiments/armInvariants.js +89 -0
- package/dist/collections/experiments/config.d.ts +3 -0
- package/dist/collections/experiments/config.js +574 -0
- package/dist/collections/experiments/createDefaults.d.ts +2 -0
- package/dist/collections/experiments/createDefaults.js +7 -0
- package/dist/collections/experiments/deleteGuard.d.ts +3 -0
- package/dist/collections/experiments/deleteGuard.js +8 -0
- package/dist/collections/experiments/editability.d.ts +4 -0
- package/dist/collections/experiments/editability.js +35 -0
- package/dist/collections/experiments/flagControlParams.d.ts +2 -0
- package/dist/collections/experiments/flagControlParams.js +32 -0
- package/dist/collections/experiments/lockHook.d.ts +2 -0
- package/dist/collections/experiments/lockHook.js +134 -0
- package/dist/collections/identities.d.ts +3 -0
- package/dist/collections/identities.js +30 -0
- package/dist/collections/index.d.ts +6 -0
- package/dist/collections/index.js +24 -0
- package/dist/collections/layers.d.ts +3 -0
- package/dist/collections/layers.js +77 -0
- package/dist/collections/metrics.d.ts +3 -0
- package/dist/collections/metrics.js +115 -0
- package/dist/collections/phases.d.ts +3 -0
- package/dist/collections/phases.js +59 -0
- package/dist/collections/serialize.d.ts +5 -0
- package/dist/collections/serialize.js +40 -0
- package/dist/collections/snapshots.d.ts +3 -0
- package/dist/collections/snapshots.js +47 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +7 -0
- package/dist/db/adapter.d.ts +3 -0
- package/dist/db/adapter.js +9 -0
- package/dist/db/appends.d.ts +58 -0
- package/dist/db/appends.js +116 -0
- package/dist/db/delete.d.ts +17 -0
- package/dist/db/delete.js +35 -0
- package/dist/db/erase.d.ts +12 -0
- package/dist/db/erase.js +55 -0
- package/dist/db/events.d.ts +21 -0
- package/dist/db/events.js +100 -0
- package/dist/db/executor.d.ts +10 -0
- package/dist/db/executor.js +37 -0
- package/dist/db/identities.d.ts +11 -0
- package/dist/db/identities.js +29 -0
- package/dist/db/locks.d.ts +15 -0
- package/dist/db/locks.js +23 -0
- package/dist/db/manifest.d.ts +8 -0
- package/dist/db/manifest.js +8 -0
- package/dist/db/names.d.ts +271 -0
- package/dist/db/names.js +270 -0
- package/dist/db/pgExecutor.d.ts +41 -0
- package/dist/db/pgExecutor.js +250 -0
- package/dist/db/results.d.ts +53 -0
- package/dist/db/results.js +103 -0
- package/dist/db/slices.d.ts +18 -0
- package/dist/db/slices.js +51 -0
- package/dist/db/snapshots.d.ts +47 -0
- package/dist/db/snapshots.js +89 -0
- package/dist/db/sql.d.ts +5 -0
- package/dist/db/sql.js +5 -0
- package/dist/db/state.d.ts +12 -0
- package/dist/db/state.js +27 -0
- package/dist/db/sweep.d.ts +13 -0
- package/dist/db/sweep.js +90 -0
- package/dist/db/txid.d.ts +7 -0
- package/dist/db/txid.js +12 -0
- package/dist/define.d.ts +3 -0
- package/dist/define.js +6 -0
- package/dist/endpoints/events.d.ts +7 -0
- package/dist/endpoints/events.js +98 -0
- package/dist/endpoints/index.d.ts +18 -0
- package/dist/endpoints/index.js +160 -0
- package/dist/endpoints/manifest.d.ts +7 -0
- package/dist/endpoints/manifest.js +30 -0
- package/dist/endpoints/preflight.d.ts +8 -0
- package/dist/endpoints/preflight.js +25 -0
- package/dist/endpoints/previewToken.d.ts +7 -0
- package/dist/endpoints/previewToken.js +54 -0
- package/dist/endpoints/recompute.d.ts +7 -0
- package/dist/endpoints/recompute.js +41 -0
- package/dist/endpoints/results.d.ts +24 -0
- package/dist/endpoints/results.js +136 -0
- package/dist/endpoints/summary.d.ts +7 -0
- package/dist/endpoints/summary.js +40 -0
- package/dist/endpoints/transitions.d.ts +42 -0
- package/dist/endpoints/transitions.js +154 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +33 -0
- package/dist/events/body.d.ts +7 -0
- package/dist/events/body.js +43 -0
- package/dist/events/dimensions.d.ts +4 -0
- package/dist/events/dimensions.js +51 -0
- package/dist/events/preview.d.ts +2 -0
- package/dist/events/preview.js +9 -0
- package/dist/events/request.d.ts +3 -0
- package/dist/events/request.js +14 -0
- package/dist/events/rows.d.ts +7 -0
- package/dist/events/rows.js +65 -0
- package/dist/exports/adminClient.d.ts +16 -0
- package/dist/exports/adminClient.js +17 -0
- package/dist/exports/client.d.ts +7 -0
- package/dist/exports/client.js +7 -0
- package/dist/exports/index.d.ts +4 -0
- package/dist/exports/index.js +3 -0
- package/dist/exports/proxy.d.ts +4 -0
- package/dist/exports/proxy.js +3 -0
- package/dist/exports/puck.d.ts +4 -0
- package/dist/exports/puck.js +3 -0
- package/dist/exports/puckEditor.d.ts +1 -0
- package/dist/exports/puckEditor.js +2 -0
- package/dist/exports/rsc.d.ts +13 -0
- package/dist/exports/rsc.js +13 -0
- package/dist/exports/runtimeProxy.d.ts +4 -0
- package/dist/exports/runtimeProxy.js +2 -0
- package/dist/exports/runtimeServer.d.ts +2 -0
- package/dist/exports/runtimeServer.js +1 -0
- package/dist/fields/armSelect.d.ts +7 -0
- package/dist/fields/armSelect.js +33 -0
- package/dist/globals/state.d.ts +3 -0
- package/dist/globals/state.js +31 -0
- package/dist/hooks/contentChanges.d.ts +3 -0
- package/dist/hooks/contentChanges.js +10 -0
- package/dist/hooks/hostHook.d.ts +5 -0
- package/dist/hooks/hostHook.js +20 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/manifest/cache.d.ts +10 -0
- package/dist/manifest/cache.js +51 -0
- package/dist/manifest/compile.d.ts +63 -0
- package/dist/manifest/compile.js +237 -0
- package/dist/onInit.d.ts +5 -0
- package/dist/onInit.js +13 -0
- package/dist/options/fromReq.d.ts +3 -0
- package/dist/options/fromReq.js +5 -0
- package/dist/options.d.ts +9 -0
- package/dist/options.js +99 -0
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.js +62 -0
- package/dist/proxy/apply.d.ts +3 -0
- package/dist/proxy/apply.js +5 -0
- package/dist/proxy/consentFallback.d.ts +5 -0
- package/dist/proxy/consentFallback.js +27 -0
- package/dist/proxy/control.d.ts +6 -0
- package/dist/proxy/control.js +36 -0
- package/dist/proxy/createProxy.d.ts +3 -0
- package/dist/proxy/createProxy.js +15 -0
- package/dist/proxy/pageRewrite.d.ts +4 -0
- package/dist/proxy/pageRewrite.js +41 -0
- package/dist/proxy/resolve.d.ts +13 -0
- package/dist/proxy/resolve.js +163 -0
- package/dist/proxy/skip.d.ts +1 -0
- package/dist/proxy/skip.js +9 -0
- package/dist/puck/ExperimentPicker.d.ts +5 -0
- package/dist/puck/ExperimentPicker.js +88 -0
- package/dist/puck/ExperimentSlot.d.ts +3 -0
- package/dist/puck/ExperimentSlot.js +16 -0
- package/dist/puck/copy.d.ts +14 -0
- package/dist/puck/copy.js +14 -0
- package/dist/puck/editor/CreateExperimentDrawer.d.ts +5 -0
- package/dist/puck/editor/CreateExperimentDrawer.js +120 -0
- package/dist/puck/editor/ExperimentSlotEditor.d.ts +3 -0
- package/dist/puck/editor/ExperimentSlotEditor.js +225 -0
- package/dist/puck/editor/cloneSlot.d.ts +3 -0
- package/dist/puck/editor/cloneSlot.js +41 -0
- package/dist/puck/editor/copy.d.ts +25 -0
- package/dist/puck/editor/copy.js +25 -0
- package/dist/puck/editor/editor.model.d.ts +16 -0
- package/dist/puck/editor/editor.model.js +33 -0
- package/dist/puck/experimentSlotField.d.ts +5 -0
- package/dist/puck/experimentSlotField.js +13 -0
- package/dist/puck/slot.model.d.ts +2 -0
- package/dist/puck/slot.model.js +11 -0
- package/dist/puck/slotConfig.d.ts +5 -0
- package/dist/puck/slotConfig.js +25 -0
- package/dist/results/aggregate.d.ts +34 -0
- package/dist/results/aggregate.js +66 -0
- package/dist/results/analyse.d.ts +79 -0
- package/dist/results/analyse.js +378 -0
- package/dist/results/cuped.d.ts +16 -0
- package/dist/results/cuped.js +31 -0
- package/dist/results/decideGuard.d.ts +3 -0
- package/dist/results/decideGuard.js +8 -0
- package/dist/results/guardrails.d.ts +17 -0
- package/dist/results/guardrails.js +10 -0
- package/dist/results/history.d.ts +13 -0
- package/dist/results/history.js +39 -0
- package/dist/results/identityCredit.d.ts +16 -0
- package/dist/results/identityCredit.js +46 -0
- package/dist/results/power.d.ts +15 -0
- package/dist/results/power.js +60 -0
- package/dist/results/rolloutUnits.d.ts +17 -0
- package/dist/results/rolloutUnits.js +26 -0
- package/dist/results/snapshot.d.ts +9 -0
- package/dist/results/snapshot.js +154 -0
- package/dist/results/srm.d.ts +9 -0
- package/dist/results/srm.js +22 -0
- package/dist/results/verdict.d.ts +82 -0
- package/dist/results/verdict.js +85 -0
- package/dist/runtime/assign.d.ts +23 -0
- package/dist/runtime/assign.js +104 -0
- package/dist/runtime/bot.d.ts +1 -0
- package/dist/runtime/bot.js +7 -0
- package/dist/runtime/cookie.d.ts +17 -0
- package/dist/runtime/cookie.js +35 -0
- package/dist/runtime/eligibility.d.ts +22 -0
- package/dist/runtime/eligibility.js +20 -0
- package/dist/runtime/header.d.ts +17 -0
- package/dist/runtime/header.js +97 -0
- package/dist/runtime/loaders.d.ts +17 -0
- package/dist/runtime/loaders.js +29 -0
- package/dist/runtime/normalisePath.d.ts +7 -0
- package/dist/runtime/normalisePath.js +15 -0
- package/dist/runtime/previewToken.d.ts +9 -0
- package/dist/runtime/previewToken.js +23 -0
- package/dist/runtime/proxyRuntime.d.ts +2 -0
- package/dist/runtime/proxyRuntime.js +22 -0
- package/dist/runtime/seed.d.ts +2 -0
- package/dist/runtime/seed.js +4 -0
- package/dist/runtime/signing.d.ts +3 -0
- package/dist/runtime/signing.js +16 -0
- package/dist/runtime/targeting.d.ts +6 -0
- package/dist/runtime/targeting.js +9 -0
- package/dist/server/context.d.ts +10 -0
- package/dist/server/context.js +59 -0
- package/dist/server/eraseUser.d.ts +7 -0
- package/dist/server/eraseUser.js +4 -0
- package/dist/server/getAssignments.d.ts +9 -0
- package/dist/server/getAssignments.js +227 -0
- package/dist/server/getFlagPoint.d.ts +11 -0
- package/dist/server/getFlagPoint.js +36 -0
- package/dist/server/headerRevision.d.ts +6 -0
- package/dist/server/headerRevision.js +10 -0
- package/dist/server/linkIdentity.d.ts +9 -0
- package/dist/server/linkIdentity.js +34 -0
- package/dist/server/metadata.d.ts +7 -0
- package/dist/server/metadata.js +24 -0
- package/dist/server/previewDraft.d.ts +7 -0
- package/dist/server/previewDraft.js +51 -0
- package/dist/server/recordContentChange.d.ts +7 -0
- package/dist/server/recordContentChange.js +150 -0
- package/dist/server/recordExposures.d.ts +7 -0
- package/dist/server/recordExposures.js +56 -0
- package/dist/server/request.d.ts +7 -0
- package/dist/server/request.js +39 -0
- package/dist/server/resolveExperimentData.d.ts +6 -0
- package/dist/server/resolveExperimentData.js +64 -0
- package/dist/server/runResults.d.ts +22 -0
- package/dist/server/runResults.js +307 -0
- package/dist/server/serverRuntime.d.ts +7 -0
- package/dist/server/serverRuntime.js +20 -0
- package/dist/server/sitemap.d.ts +3 -0
- package/dist/server/sitemap.js +6 -0
- package/dist/server/toClientAssignments.d.ts +7 -0
- package/dist/server/toClientAssignments.js +56 -0
- package/dist/server/trackConversion.d.ts +12 -0
- package/dist/server/trackConversion.js +69 -0
- package/dist/targeting/ops.d.ts +8 -0
- package/dist/targeting/ops.js +58 -0
- package/dist/transitions/addMetric.d.ts +16 -0
- package/dist/transitions/addMetric.js +126 -0
- package/dist/transitions/allocate.d.ts +9 -0
- package/dist/transitions/allocate.js +84 -0
- package/dist/transitions/archive.d.ts +9 -0
- package/dist/transitions/archive.js +46 -0
- package/dist/transitions/checkStart.d.ts +61 -0
- package/dist/transitions/checkStart.js +104 -0
- package/dist/transitions/confirmPhrases.d.ts +5 -0
- package/dist/transitions/confirmPhrases.js +18 -0
- package/dist/transitions/decide.d.ts +9 -0
- package/dist/transitions/decide.js +138 -0
- package/dist/transitions/deleteExperiment.d.ts +5 -0
- package/dist/transitions/deleteExperiment.js +66 -0
- package/dist/transitions/kernel.d.ts +31 -0
- package/dist/transitions/kernel.js +69 -0
- package/dist/transitions/makeContent.d.ts +20 -0
- package/dist/transitions/makeContent.js +411 -0
- package/dist/transitions/phaseRecord.d.ts +27 -0
- package/dist/transitions/phaseRecord.js +81 -0
- package/dist/transitions/placements.d.ts +26 -0
- package/dist/transitions/placements.js +89 -0
- package/dist/transitions/preflightData.d.ts +21 -0
- package/dist/transitions/preflightData.js +317 -0
- package/dist/transitions/releaseHoldout.d.ts +8 -0
- package/dist/transitions/releaseHoldout.js +30 -0
- package/dist/transitions/resolveCaps.d.ts +28 -0
- package/dist/transitions/resolveCaps.js +68 -0
- package/dist/transitions/start.d.ts +26 -0
- package/dist/transitions/start.js +220 -0
- package/dist/transitions/stop.d.ts +8 -0
- package/dist/transitions/stop.js +25 -0
- package/dist/types/admin.d.ts +20 -0
- package/dist/types/admin.js +1 -0
- package/dist/types/assignments.d.ts +57 -0
- package/dist/types/assignments.js +1 -0
- package/dist/types/contracts.d.ts +133 -0
- package/dist/types/contracts.js +2 -0
- package/dist/types/endpoints.d.ts +135 -0
- package/dist/types/endpoints.js +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.js +8 -0
- package/dist/types/manifest.d.ts +42 -0
- package/dist/types/manifest.js +1 -0
- package/dist/types/puck.d.ts +23 -0
- package/dist/types/puck.js +1 -0
- package/dist/types/runtime.d.ts +59 -0
- package/dist/types/runtime.js +1 -0
- package/dist/types/snapshot.d.ts +117 -0
- package/dist/types/snapshot.js +1 -0
- package/package.json +137 -3
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { txExecutor } from '../db/executor.js';
|
|
2
|
+
import { deleteExperimentSnapshots, serializeDelete } from '../db/delete.js';
|
|
3
|
+
import { deleteExperimentEvents } from '../db/events.js';
|
|
4
|
+
import { lockTransitionScope } from '../db/locks.js';
|
|
5
|
+
import { buildAndStoreManifest } from '../manifest/compile.js';
|
|
6
|
+
import { readExperiment, refuse } from './start.js';
|
|
7
|
+
/** Payload deleteByID owns the request transaction; never commit inside hooks. */ export async function deleteExperiment(req, experimentId) {
|
|
8
|
+
await serializeDelete(req, async (state)=>{
|
|
9
|
+
const exec = txExecutor(req);
|
|
10
|
+
await lockTransitionScope(exec, {
|
|
11
|
+
experimentId
|
|
12
|
+
});
|
|
13
|
+
const current = await readExperiment(req, experimentId);
|
|
14
|
+
if (current.status !== 'draft' && current.status !== 'archived') refuse('invalid-status');
|
|
15
|
+
try {
|
|
16
|
+
const collection = 'xp-phases';
|
|
17
|
+
// §7.8 transition exception: cascade while FK references still exist.
|
|
18
|
+
const { docs } = await req.payload.find({
|
|
19
|
+
collection,
|
|
20
|
+
req,
|
|
21
|
+
overrideAccess: true,
|
|
22
|
+
depth: 0,
|
|
23
|
+
limit: 0,
|
|
24
|
+
where: {
|
|
25
|
+
experiment: {
|
|
26
|
+
equals: experimentId
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
// deleteByID throws on a hook failure; bulk delete swallows per-doc errors.
|
|
31
|
+
for (const doc of docs){
|
|
32
|
+
// §7.8: privileged plugin cascade shares the access-checked delete's req.
|
|
33
|
+
await req.payload.delete({
|
|
34
|
+
collection,
|
|
35
|
+
id: doc.id,
|
|
36
|
+
req,
|
|
37
|
+
overrideAccess: true
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
await deleteExperimentSnapshots(exec, experimentId);
|
|
41
|
+
await deleteExperimentEvents(exec, experimentId);
|
|
42
|
+
state.excludedIds.set(String(experimentId), experimentId);
|
|
43
|
+
const decision = current.decision;
|
|
44
|
+
if (current.status !== 'archived' || decision?.discardedAt) state.pruneFlagDefaultIds.set(String(experimentId), experimentId);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
req.payload.logger.error({
|
|
47
|
+
experimentId,
|
|
48
|
+
err: error
|
|
49
|
+
}, 'Experiment cascade failed; aborting delete');
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export async function deleteExperimentAfter(req, experimentId) {
|
|
55
|
+
await serializeDelete(req, async (state)=>{
|
|
56
|
+
await buildAndStoreManifest(req, {
|
|
57
|
+
excludeIds: [
|
|
58
|
+
...state.excludedIds.values(),
|
|
59
|
+
experimentId
|
|
60
|
+
],
|
|
61
|
+
pruneFlagDefaultIds: [
|
|
62
|
+
...state.pruneFlagDefaultIds.values()
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type PayloadRequest } from 'payload';
|
|
2
|
+
import { type Executor } from '../db/executor.js';
|
|
3
|
+
import type { DocId } from '../types/contracts.js';
|
|
4
|
+
export interface TransitionScope {
|
|
5
|
+
experimentId: DocId;
|
|
6
|
+
layerId?: DocId | null;
|
|
7
|
+
flagPointId?: string | null;
|
|
8
|
+
/** Defaults to true; content-change audit appends opt out. */
|
|
9
|
+
bump?: boolean;
|
|
10
|
+
/** Best-effort content audit only; a borrowed transaction must never be killed here. */
|
|
11
|
+
audit?: boolean;
|
|
12
|
+
excludeIds?: DocId[];
|
|
13
|
+
}
|
|
14
|
+
/** Runs transition writes on the request's transaction under ordered locks.
|
|
15
|
+
* Returns the callback result and, unless bump is false, the stored manifest's
|
|
16
|
+
* revision. Only the transaction owner commits and verifies that transaction's
|
|
17
|
+
* status on the pool (even without a bump); a caller-owned transaction still
|
|
18
|
+
* needs its caller to commit. The explicit borrowed audit path takes only lock E
|
|
19
|
+
* inside a savepoint. ROLLBACK TO SAVEPOINT releases that transaction advisory
|
|
20
|
+
* lock (Postgres §13.3); after RELEASE it remains held until the host commits.
|
|
21
|
+
* Audit failures never kill the host transaction, even if savepoint recovery
|
|
22
|
+
* itself fails: the host must observe its own failed commit. Other failures kill
|
|
23
|
+
* even a borrowed transaction. Owned audits follow the ordinary owned path.
|
|
24
|
+
*/
|
|
25
|
+
export declare function withTransition<T>(req: PayloadRequest, scope: TransitionScope, fn: (ctx: {
|
|
26
|
+
exec: Executor;
|
|
27
|
+
req: PayloadRequest;
|
|
28
|
+
}) => Promise<T>): Promise<{
|
|
29
|
+
result: T;
|
|
30
|
+
revision?: number;
|
|
31
|
+
}>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { commitTransaction, initTransaction, killTransaction } from 'payload';
|
|
2
|
+
import { pooledExecutor, txExecutor } from '../db/executor.js';
|
|
3
|
+
import { lockTransitionScope } from '../db/locks.js';
|
|
4
|
+
import { currentTxid, txidStatus } from '../db/txid.js';
|
|
5
|
+
import { contentChangeSavepoint } from '../db/appends.js';
|
|
6
|
+
import { XpRefusal } from '../errors.js';
|
|
7
|
+
import { buildAndStoreManifest } from '../manifest/compile.js';
|
|
8
|
+
/** Runs transition writes on the request's transaction under ordered locks.
|
|
9
|
+
* Returns the callback result and, unless bump is false, the stored manifest's
|
|
10
|
+
* revision. Only the transaction owner commits and verifies that transaction's
|
|
11
|
+
* status on the pool (even without a bump); a caller-owned transaction still
|
|
12
|
+
* needs its caller to commit. The explicit borrowed audit path takes only lock E
|
|
13
|
+
* inside a savepoint. ROLLBACK TO SAVEPOINT releases that transaction advisory
|
|
14
|
+
* lock (Postgres §13.3); after RELEASE it remains held until the host commits.
|
|
15
|
+
* Audit failures never kill the host transaction, even if savepoint recovery
|
|
16
|
+
* itself fails: the host must observe its own failed commit. Other failures kill
|
|
17
|
+
* even a borrowed transaction. Owned audits follow the ordinary owned path.
|
|
18
|
+
*/ export async function withTransition(req, scope, fn) {
|
|
19
|
+
const hadTransition = Object.prototype.hasOwnProperty.call(req.context, 'xpTransition');
|
|
20
|
+
const previousTransition = req.context.xpTransition;
|
|
21
|
+
let borrowedAudit = scope.audit === true && req.transactionID != null;
|
|
22
|
+
try {
|
|
23
|
+
const owns = await initTransaction(req);
|
|
24
|
+
borrowedAudit = scope.audit === true && !owns;
|
|
25
|
+
const exec = txExecutor(req);
|
|
26
|
+
req.context.xpTransition = true;
|
|
27
|
+
const work = async ()=>{
|
|
28
|
+
await lockTransitionScope(exec, scope);
|
|
29
|
+
return fn({
|
|
30
|
+
exec,
|
|
31
|
+
req
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
let result;
|
|
35
|
+
if (borrowedAudit) {
|
|
36
|
+
const audit = await contentChangeSavepoint(exec, work);
|
|
37
|
+
if (!audit.ok) {
|
|
38
|
+
throw audit.error;
|
|
39
|
+
}
|
|
40
|
+
result = audit.result;
|
|
41
|
+
} else result = await work();
|
|
42
|
+
const revision = scope.bump === false ? undefined : (await buildAndStoreManifest(req, {
|
|
43
|
+
excludeIds: scope.excludeIds
|
|
44
|
+
})).revision;
|
|
45
|
+
if (owns) {
|
|
46
|
+
const xid = await currentTxid(exec);
|
|
47
|
+
await commitTransaction(req);
|
|
48
|
+
const status = await txidStatus(pooledExecutor(req.payload), xid);
|
|
49
|
+
if (status !== 'committed') {
|
|
50
|
+
throw new XpRefusal('commit-unverified', 'The transition commit could not be verified.', {
|
|
51
|
+
xid,
|
|
52
|
+
status
|
|
53
|
+
}, 500);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return revision === undefined ? {
|
|
57
|
+
result
|
|
58
|
+
} : {
|
|
59
|
+
result,
|
|
60
|
+
revision
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (!borrowedAudit) await killTransaction(req);
|
|
64
|
+
throw error;
|
|
65
|
+
} finally{
|
|
66
|
+
if (hadTransition) req.context.xpTransition = previousTransition;
|
|
67
|
+
else delete req.context.xpTransition;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type PayloadRequest } from 'payload';
|
|
2
|
+
import type { DocId, ResolvedOptions } from '../types/contracts.js';
|
|
3
|
+
type ContentDoc = {
|
|
4
|
+
collection: string;
|
|
5
|
+
id: DocId;
|
|
6
|
+
ok: boolean;
|
|
7
|
+
detail?: string;
|
|
8
|
+
error?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function makeContent<TUser extends {
|
|
11
|
+
id: DocId;
|
|
12
|
+
}>(req: PayloadRequest, { experimentId, options }: {
|
|
13
|
+
experimentId: DocId;
|
|
14
|
+
options: ResolvedOptions<TUser>;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
docs: ContentDoc[];
|
|
17
|
+
doc: import("payload").JsonObject & import("payload").TypeWithID;
|
|
18
|
+
revision: number;
|
|
19
|
+
}>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { commitTransaction, initTransaction, killTransaction } from 'payload';
|
|
2
|
+
import { lockContentDefaults } from '../db/appends.js';
|
|
3
|
+
import { pooledExecutor, txExecutor } from '../db/executor.js';
|
|
4
|
+
import { currentTxid, txidStatus } from '../db/txid.js';
|
|
5
|
+
import { buildAndStoreManifest } from '../manifest/compile.js';
|
|
6
|
+
import { puckCollections } from '../options.js';
|
|
7
|
+
import { archive } from './archive.js';
|
|
8
|
+
import { relationshipId } from './checkStart.js';
|
|
9
|
+
import { withTransition } from './kernel.js';
|
|
10
|
+
import { scanBlockPlacements } from './placements.js';
|
|
11
|
+
import { readExperiment, refuse, updateExperiment } from './start.js';
|
|
12
|
+
const object = (value)=>value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
13
|
+
/** Flatten selected slots in every content array, including nested selected slots. */ function unwrap(value, slug, slot) {
|
|
14
|
+
if (Array.isArray(value)) return value.flatMap((node)=>{
|
|
15
|
+
if (object(node) && node.type === 'ExperimentSlot' && object(node.props)) {
|
|
16
|
+
const ref = node.props.experiment;
|
|
17
|
+
if ((typeof ref === 'string' ? ref : object(ref) ? ref.slug : undefined) === slug) {
|
|
18
|
+
const content = node.props[slot];
|
|
19
|
+
if (!Array.isArray(content)) throw new Error('The winning content slot is missing.');
|
|
20
|
+
return unwrap(content, slug, slot);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return [
|
|
24
|
+
unwrap(node, slug, slot)
|
|
25
|
+
];
|
|
26
|
+
});
|
|
27
|
+
if (object(value)) return Object.fromEntries(Object.entries(value).map(([key, child])=>[
|
|
28
|
+
key,
|
|
29
|
+
unwrap(child, slug, slot)
|
|
30
|
+
]));
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
/** Payload update merges omissions from the latest version even on publish.
|
|
34
|
+
* Restore each captured document's fields, omitting API/adapter metadata.
|
|
35
|
+
*/ function contentData(doc, field, content) {
|
|
36
|
+
const metadata = new Set([
|
|
37
|
+
'id',
|
|
38
|
+
'createdAt',
|
|
39
|
+
'updatedAt',
|
|
40
|
+
'_isLocked',
|
|
41
|
+
'_userEditing'
|
|
42
|
+
]);
|
|
43
|
+
return {
|
|
44
|
+
...Object.fromEntries(Object.entries(doc).filter(([key])=>!metadata.has(key))),
|
|
45
|
+
[field]: content
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Refuse unsafe collection shapes before rebuilding complete host documents. */ async function fieldRefusal(fields, req, doc, operation = 'read', data = doc, siblingData = data, blockData, seen = new Set()) {
|
|
49
|
+
for (const field of fields){
|
|
50
|
+
if (seen.has(field)) continue;
|
|
51
|
+
const branch = new Set(seen).add(field);
|
|
52
|
+
if ('localized' in field && field.localized) return 'This page is localized; update it by hand.';
|
|
53
|
+
if ('hidden' in field && field.hidden === true) return 'This page has hidden fields; update it by hand.';
|
|
54
|
+
if ('access' in field && field.access?.[operation] && !await field.access[operation]({
|
|
55
|
+
id: doc.id,
|
|
56
|
+
req,
|
|
57
|
+
doc,
|
|
58
|
+
data,
|
|
59
|
+
siblingData,
|
|
60
|
+
blockData
|
|
61
|
+
})) {
|
|
62
|
+
return operation === 'read' ? 'This page has fields you cannot read; update it by hand.' : 'This page has fields you cannot update; update it by hand.';
|
|
63
|
+
}
|
|
64
|
+
const value = 'name' in field ? siblingData[field.name] : siblingData;
|
|
65
|
+
if ('tabs' in field) for (const tab of field.tabs){
|
|
66
|
+
if ('localized' in tab && tab.localized) return 'This page is localized; update it by hand.';
|
|
67
|
+
if ('hidden' in tab && tab.hidden === true) return 'This page has hidden fields; update it by hand.';
|
|
68
|
+
if ('access' in tab && tab.access?.[operation] && !await tab.access[operation]({
|
|
69
|
+
id: doc.id,
|
|
70
|
+
req,
|
|
71
|
+
doc,
|
|
72
|
+
data,
|
|
73
|
+
siblingData,
|
|
74
|
+
blockData
|
|
75
|
+
})) {
|
|
76
|
+
return operation === 'read' ? 'This page has fields you cannot read; update it by hand.' : 'This page has fields you cannot update; update it by hand.';
|
|
77
|
+
}
|
|
78
|
+
const tabData = 'name' in tab && tab.name ? siblingData[tab.name] : siblingData;
|
|
79
|
+
const refusal = await fieldRefusal(tab.fields, req, doc, operation, data, object(tabData) ? tabData : {}, blockData, branch);
|
|
80
|
+
if (refusal) return refusal;
|
|
81
|
+
}
|
|
82
|
+
if ('fields' in field) for (const row of Array.isArray(value) && value.length ? value : [
|
|
83
|
+
value
|
|
84
|
+
]){
|
|
85
|
+
const refusal = await fieldRefusal(field.fields, req, doc, operation, data, object(row) ? row : {}, blockData, branch);
|
|
86
|
+
if (refusal) return refusal;
|
|
87
|
+
}
|
|
88
|
+
if (field.type === 'blocks') {
|
|
89
|
+
const blocks = [
|
|
90
|
+
...field.blocks,
|
|
91
|
+
...(field.blockReferences ?? []).map((slug)=>req.payload.config.blocks?.find((block)=>block.slug === slug)).filter((block)=>block !== undefined)
|
|
92
|
+
];
|
|
93
|
+
for (const block of blocks){
|
|
94
|
+
const rows = Array.isArray(value) ? value.filter((row)=>object(row) && row.blockType === block.slug) : [];
|
|
95
|
+
for (const row of rows.length ? rows : [
|
|
96
|
+
{}
|
|
97
|
+
]){
|
|
98
|
+
const refusal = await fieldRefusal(block.fields, req, doc, operation, data, row, row, branch);
|
|
99
|
+
if (refusal) return refusal;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** Each placement commits independently while the outer transition retains its
|
|
106
|
+
* experiment locks. Keep the manager's exact req/user, temporarily lending it a
|
|
107
|
+
* fresh transaction; a denied host write must not kill the outer transaction.
|
|
108
|
+
*/ async function hostTransaction(req, fn) {
|
|
109
|
+
const transactionID = req.transactionID;
|
|
110
|
+
const transition = req.context.xpTransition;
|
|
111
|
+
delete req.transactionID;
|
|
112
|
+
delete req.context.xpTransition;
|
|
113
|
+
try {
|
|
114
|
+
if (!await initTransaction(req)) throw new Error('A content transaction could not be started.');
|
|
115
|
+
await fn();
|
|
116
|
+
const xid = await currentTxid(txExecutor(req));
|
|
117
|
+
await commitTransaction(req);
|
|
118
|
+
if (await txidStatus(pooledExecutor(req.payload), xid) !== 'committed') throw new Error('The content commit could not be verified. Retry to check the saved content.');
|
|
119
|
+
} catch (error) {
|
|
120
|
+
await killTransaction(req);
|
|
121
|
+
throw error;
|
|
122
|
+
} finally{
|
|
123
|
+
req.transactionID = transactionID;
|
|
124
|
+
if (transition === undefined) delete req.context.xpTransition;
|
|
125
|
+
else req.context.xpTransition = transition;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
export async function makeContent(req, { experimentId, options }) {
|
|
129
|
+
const before = await readExperiment(req, experimentId);
|
|
130
|
+
const { result } = await withTransition(req, {
|
|
131
|
+
experimentId,
|
|
132
|
+
layerId: relationshipId(before.layer),
|
|
133
|
+
flagPointId: before.kind === 'flag' ? before.flagPoint : undefined,
|
|
134
|
+
bump: false
|
|
135
|
+
}, async ({ exec })=>{
|
|
136
|
+
const current = await readExperiment(req, experimentId);
|
|
137
|
+
if (current.status !== 'decided' || ![
|
|
138
|
+
'block',
|
|
139
|
+
'flag',
|
|
140
|
+
'page'
|
|
141
|
+
].includes(current.kind)) refuse('invalid-status');
|
|
142
|
+
const decision = current.decision;
|
|
143
|
+
const winner = current.arms.find((arm)=>arm.key === decision?.winnerArm && arm.kind !== 'holdout');
|
|
144
|
+
if (!winner) refuse('invalid-winner');
|
|
145
|
+
const docs = [];
|
|
146
|
+
const attempt = async (collection, id, fn)=>{
|
|
147
|
+
try {
|
|
148
|
+
await hostTransaction(req, fn);
|
|
149
|
+
docs.push({
|
|
150
|
+
collection,
|
|
151
|
+
id,
|
|
152
|
+
ok: true
|
|
153
|
+
});
|
|
154
|
+
} catch (error) {
|
|
155
|
+
const detail = error instanceof Error ? error.message : 'The content could not be updated.';
|
|
156
|
+
docs.push({
|
|
157
|
+
collection,
|
|
158
|
+
id,
|
|
159
|
+
ok: false,
|
|
160
|
+
detail,
|
|
161
|
+
error: detail
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
if (current.kind === 'block') {
|
|
166
|
+
if (!winner.slot) refuse('invalid-winner');
|
|
167
|
+
const report = decision?.contentReport;
|
|
168
|
+
const registrations = puckCollections(options);
|
|
169
|
+
const scanned = [];
|
|
170
|
+
for (const registration of registrations){
|
|
171
|
+
const transactionID = req.transactionID;
|
|
172
|
+
delete req.transactionID;
|
|
173
|
+
try {
|
|
174
|
+
scanned.push(...await scanBlockPlacements(req, {
|
|
175
|
+
slug: current.slug,
|
|
176
|
+
puckCollections: [
|
|
177
|
+
registration
|
|
178
|
+
]
|
|
179
|
+
}));
|
|
180
|
+
} catch (error) {
|
|
181
|
+
// A collection made unreadable since the last attempt still has
|
|
182
|
+
// retained placements to report below. Never sacrifice the outer tx.
|
|
183
|
+
const denied = error && typeof error === 'object' && 'status' in error && error.status === 403;
|
|
184
|
+
if (!denied || !report?.docs?.some((doc)=>doc.collection === registration.slug)) throw error;
|
|
185
|
+
} finally{
|
|
186
|
+
req.transactionID = transactionID;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (scanned.some((doc)=>doc.locked)) refuse('locked-placement');
|
|
190
|
+
const placements = new Map();
|
|
191
|
+
for (const doc of [
|
|
192
|
+
...report?.docs ?? [],
|
|
193
|
+
...scanned
|
|
194
|
+
])placements.set(doc.collection + ':' + doc.id, doc);
|
|
195
|
+
const skipped = new Set();
|
|
196
|
+
// Probe with no borrowed transaction: disableErrors handles expected
|
|
197
|
+
// NotFound/Forbidden, and even a host read hook cannot kill the outer tx.
|
|
198
|
+
for (const [key, { collection, id }] of placements){
|
|
199
|
+
const transactionID = req.transactionID;
|
|
200
|
+
delete req.transactionID;
|
|
201
|
+
try {
|
|
202
|
+
const config = req.payload.config.collections.find((config)=>config.slug === collection);
|
|
203
|
+
const access = config?.access?.read ? await config.access.read({
|
|
204
|
+
id,
|
|
205
|
+
req
|
|
206
|
+
}) : true;
|
|
207
|
+
const doc = await req.payload.findByID({
|
|
208
|
+
collection,
|
|
209
|
+
id,
|
|
210
|
+
req,
|
|
211
|
+
overrideAccess: false,
|
|
212
|
+
depth: 0,
|
|
213
|
+
draft: true,
|
|
214
|
+
includeLockStatus: true,
|
|
215
|
+
disableErrors: true
|
|
216
|
+
});
|
|
217
|
+
if (!doc) {
|
|
218
|
+
// Fail closed (exp-orch 16:20): only an unfiltered read proves the page is gone;
|
|
219
|
+
// a Where-filtered miss may be a page this manager cannot see.
|
|
220
|
+
const ok = access === true || typeof access === 'object' && access !== null && Object.keys(access).length === 0;
|
|
221
|
+
const detail = ok ? 'This page no longer exists; nothing to change.' : access === false ? 'You can no longer read this page.' : 'You can no longer read this page. If it was deleted, archive this experiment with discard or hard-coded instead.';
|
|
222
|
+
docs.push({
|
|
223
|
+
collection,
|
|
224
|
+
id,
|
|
225
|
+
ok,
|
|
226
|
+
detail,
|
|
227
|
+
...!ok ? {
|
|
228
|
+
error: detail
|
|
229
|
+
} : {}
|
|
230
|
+
});
|
|
231
|
+
skipped.add(key);
|
|
232
|
+
} else if (doc._isLocked === true || doc.locked === true) refuse('locked-placement', {
|
|
233
|
+
collection,
|
|
234
|
+
id
|
|
235
|
+
});
|
|
236
|
+
} catch (error) {
|
|
237
|
+
if (error && typeof error === 'object' && 'code' in error && error.code === 'locked-placement') throw error;
|
|
238
|
+
const missing = error && typeof error === 'object' && 'status' in error && error.status === 404;
|
|
239
|
+
const detail = missing ? 'This page no longer exists; nothing to change.' : 'You can no longer read this page.';
|
|
240
|
+
docs.push({
|
|
241
|
+
collection,
|
|
242
|
+
id,
|
|
243
|
+
ok: Boolean(missing),
|
|
244
|
+
detail,
|
|
245
|
+
...!missing ? {
|
|
246
|
+
error: detail
|
|
247
|
+
} : {}
|
|
248
|
+
});
|
|
249
|
+
skipped.add(key);
|
|
250
|
+
} finally{
|
|
251
|
+
req.transactionID = transactionID;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
for (const [key, { collection, id }] of placements){
|
|
255
|
+
if (skipped.has(key)) continue;
|
|
256
|
+
await attempt(collection, id, async ()=>{
|
|
257
|
+
const field = registrations.find((entry)=>entry.slug === collection)?.field;
|
|
258
|
+
if (!field) throw new Error('This content collection is no longer configured.');
|
|
259
|
+
const draft = await req.payload.findByID({
|
|
260
|
+
collection,
|
|
261
|
+
id,
|
|
262
|
+
req,
|
|
263
|
+
overrideAccess: false,
|
|
264
|
+
depth: 0,
|
|
265
|
+
draft: true
|
|
266
|
+
});
|
|
267
|
+
const config = req.payload.config.collections.find((config)=>config.slug === collection);
|
|
268
|
+
const drafts = Boolean(config?.versions && config.versions.drafts);
|
|
269
|
+
const published = drafts ? await req.payload.findByID({
|
|
270
|
+
collection,
|
|
271
|
+
id,
|
|
272
|
+
req,
|
|
273
|
+
overrideAccess: false,
|
|
274
|
+
depth: 0,
|
|
275
|
+
draft: false
|
|
276
|
+
}) : draft;
|
|
277
|
+
const draftContent = unwrap(draft[field], current.slug, winner.slot);
|
|
278
|
+
const publishedContent = unwrap(published[field], current.slug, winner.slot);
|
|
279
|
+
const changedDraft = JSON.stringify(draftContent) !== JSON.stringify(draft[field]);
|
|
280
|
+
const changedPublished = JSON.stringify(publishedContent) !== JSON.stringify(published[field]);
|
|
281
|
+
// Nothing left to rewrite (e.g. an editor already did it by hand): done, before any refusal —
|
|
282
|
+
// but only when the content field was really read (not stripped as hidden/unreadable, not localized).
|
|
283
|
+
const contentField = topLevelField(config?.fields ?? [], field);
|
|
284
|
+
// Payload strips hidden and read-denied fields from a read, so presence in both proves readability;
|
|
285
|
+
// a localized content field compares only one locale, so it never takes the shortcut.
|
|
286
|
+
const contentReadable = Object.hasOwn(draft, field) && Object.hasOwn(published, field) && !contentField?.localized;
|
|
287
|
+
if (contentReadable && !changedDraft && !changedPublished) return;
|
|
288
|
+
for (const doc of [
|
|
289
|
+
draft,
|
|
290
|
+
published
|
|
291
|
+
]){
|
|
292
|
+
const refusal = await fieldRefusal(config?.fields ?? [], req, doc);
|
|
293
|
+
if (refusal) throw new Error(refusal);
|
|
294
|
+
}
|
|
295
|
+
const publishedData = contentData(published, field, publishedContent);
|
|
296
|
+
const draftData = {
|
|
297
|
+
...contentData(draft, field, draftContent),
|
|
298
|
+
_status: 'draft'
|
|
299
|
+
};
|
|
300
|
+
// Payload beforeValidate receives the latest version as doc, and the
|
|
301
|
+
// proposed write as data/siblingData (blockData is the proposed row).
|
|
302
|
+
const publicationRefusal = await fieldRefusal(config?.fields ?? [], req, draft, 'update', publishedData);
|
|
303
|
+
if (publicationRefusal) throw new Error(publicationRefusal);
|
|
304
|
+
if (drafts) {
|
|
305
|
+
const draftOriginal = changedPublished ? {
|
|
306
|
+
...published,
|
|
307
|
+
...publishedData
|
|
308
|
+
} : draft;
|
|
309
|
+
const draftRefusal = await fieldRefusal(config?.fields ?? [], req, draftOriginal, 'update', draftData);
|
|
310
|
+
if (draftRefusal) throw new Error(draftRefusal);
|
|
311
|
+
}
|
|
312
|
+
// Host writes are access checked with the manager's req (§7.8).
|
|
313
|
+
if (changedPublished) await req.payload.update({
|
|
314
|
+
collection,
|
|
315
|
+
id,
|
|
316
|
+
req,
|
|
317
|
+
overrideAccess: false,
|
|
318
|
+
overrideLock: false,
|
|
319
|
+
draft: false,
|
|
320
|
+
data: publishedData
|
|
321
|
+
});
|
|
322
|
+
if (drafts) await req.payload.update({
|
|
323
|
+
collection,
|
|
324
|
+
id,
|
|
325
|
+
req,
|
|
326
|
+
overrideAccess: false,
|
|
327
|
+
overrideLock: false,
|
|
328
|
+
draft: true,
|
|
329
|
+
data: draftData
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
} else if (current.kind === 'flag') {
|
|
334
|
+
const point = options.flagPoints?.find((point)=>point.id === current.flagPoint);
|
|
335
|
+
if (!point) refuse('flag-no-point');
|
|
336
|
+
if (point.makeContent) await attempt('xp-experiments', experimentId, ()=>point.makeContent({
|
|
337
|
+
experimentId,
|
|
338
|
+
winner: {
|
|
339
|
+
arm: winner.key,
|
|
340
|
+
params: winner.params ?? {}
|
|
341
|
+
},
|
|
342
|
+
req
|
|
343
|
+
}));
|
|
344
|
+
// Lock the shared state before its read/modify/write, after L/F/E.
|
|
345
|
+
await lockContentDefaults(exec);
|
|
346
|
+
// §7.8 transition exception: durable made-content defaults in plugin state.
|
|
347
|
+
const state = await req.payload.findGlobal({
|
|
348
|
+
slug: 'xp-state',
|
|
349
|
+
req,
|
|
350
|
+
overrideAccess: true,
|
|
351
|
+
depth: 0
|
|
352
|
+
});
|
|
353
|
+
// §7.8 transition exception: host documents above always use false.
|
|
354
|
+
await req.payload.updateGlobal({
|
|
355
|
+
slug: 'xp-state',
|
|
356
|
+
req,
|
|
357
|
+
overrideAccess: true,
|
|
358
|
+
data: {
|
|
359
|
+
flagDefaults: {
|
|
360
|
+
...state.flagDefaults ?? {},
|
|
361
|
+
[point.id]: {
|
|
362
|
+
winnerArm: winner.key,
|
|
363
|
+
params: winner.params ?? {},
|
|
364
|
+
experiment: experimentId,
|
|
365
|
+
decidedAt: current.decidedAt
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
const doc = await updateExperiment(req, experimentId, {
|
|
372
|
+
decision: {
|
|
373
|
+
...decision,
|
|
374
|
+
contentReport: {
|
|
375
|
+
docs
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
if (docs.every((doc)=>doc.ok)) return {
|
|
380
|
+
...await archive(req, {
|
|
381
|
+
experimentId,
|
|
382
|
+
mode: 'after-content'
|
|
383
|
+
}),
|
|
384
|
+
docs
|
|
385
|
+
};
|
|
386
|
+
const manifest = await buildAndStoreManifest(req);
|
|
387
|
+
return {
|
|
388
|
+
doc,
|
|
389
|
+
revision: manifest.revision,
|
|
390
|
+
docs
|
|
391
|
+
};
|
|
392
|
+
});
|
|
393
|
+
return result;
|
|
394
|
+
}
|
|
395
|
+
/** The top-level data field with this name, looking through unnamed rows, collapsibles and tabs. */ function topLevelField(fields, name) {
|
|
396
|
+
for (const raw of fields){
|
|
397
|
+
const field = raw;
|
|
398
|
+
if (field.name === name) return field;
|
|
399
|
+
if (!field.name && (field.type === 'row' || field.type === 'collapsible') && field.fields) {
|
|
400
|
+
const found = topLevelField(field.fields, name);
|
|
401
|
+
if (found) return found;
|
|
402
|
+
}
|
|
403
|
+
if (field.type === 'tabs') {
|
|
404
|
+
for (const tab of field.tabs ?? [])if (!tab.name && tab.fields) {
|
|
405
|
+
const found = topLevelField(tab.fields, name);
|
|
406
|
+
if (found) return found;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return undefined;
|
|
411
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
import type { Arm, DocId, TargetingRule } from '../types/contracts.js';
|
|
3
|
+
import type { AllocationChange, ContentChange } from '../db/appends.js';
|
|
4
|
+
export interface PhaseRecord {
|
|
5
|
+
experiment: DocId;
|
|
6
|
+
phase: number;
|
|
7
|
+
phaseKind: 'test' | 'rollout';
|
|
8
|
+
startedAt: string;
|
|
9
|
+
endedAt?: string | null;
|
|
10
|
+
seed: string;
|
|
11
|
+
arms: Arm[];
|
|
12
|
+
allocation: number;
|
|
13
|
+
allocationChanges?: AllocationChange[];
|
|
14
|
+
targeting: TargetingRule[];
|
|
15
|
+
metricDefinitions?: Array<Record<string, unknown>>;
|
|
16
|
+
analysis?: Record<string, unknown>;
|
|
17
|
+
contentChanges?: ContentChange[];
|
|
18
|
+
}
|
|
19
|
+
export declare function readPhaseRecord(req: PayloadRequest, experimentId: DocId, phase: number): Promise<PhaseRecord & {
|
|
20
|
+
id: DocId;
|
|
21
|
+
}>;
|
|
22
|
+
/** Caller holds the experiment lock and passes the kernel's transaction req. */
|
|
23
|
+
export declare function writePhaseRecord(req: PayloadRequest, record: PhaseRecord): Promise<import("payload").JsonObject & import("payload").TypeWithID>;
|
|
24
|
+
/** End only an open phase, preserving the original stop time on a later decide.
|
|
25
|
+
* Caller holds the experiment lock and passes the kernel's transaction req.
|
|
26
|
+
*/
|
|
27
|
+
export declare function setPhaseEndedAt(req: PayloadRequest, experimentId: DocId, phase: number, at: string): Promise<import("payload").JsonObject & import("payload").TypeWithID>;
|