@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.
- package/README.md +202 -26
- package/dist/admin/PreviewLinks.d.ts +2 -1
- package/dist/admin/PreviewLinks.js +50 -21
- package/dist/admin/TestsNavLink.d.ts +3 -1
- package/dist/admin/TestsNavLink.js +2 -2
- package/dist/admin/actions/ConfirmDialog.d.ts +7 -4
- package/dist/admin/actions/ConfirmDialog.js +37 -82
- package/dist/admin/actions/DecideDialog.d.ts +3 -1
- package/dist/admin/actions/DecideDialog.js +30 -13
- package/dist/admin/actions/MakeContentResult.d.ts +13 -2
- package/dist/admin/actions/MakeContentResult.js +115 -16
- package/dist/admin/actions/actions.model.d.ts +2 -4
- package/dist/admin/actions/actions.model.js +2 -3
- package/dist/admin/allocation.model.js +3 -5
- package/dist/admin/api.d.ts +10 -4
- package/dist/admin/api.js +11 -7
- package/dist/admin/armSelect/ArmSelectField.js +14 -22
- package/dist/admin/arms/arms.model.d.ts +1 -21
- package/dist/admin/arms/arms.model.js +1 -153
- package/dist/admin/copy/actions.d.ts +39 -3
- package/dist/admin/copy/actions.js +40 -4
- package/dist/admin/copy/armSelect.js +5 -5
- package/dist/admin/copy/arms.d.ts +1 -6
- package/dist/admin/copy/arms.js +1 -16
- package/dist/admin/copy/audience.d.ts +2 -1
- package/dist/admin/copy/audience.js +3 -2
- package/dist/admin/copy/common.d.ts +13 -6
- package/dist/admin/copy/common.js +14 -8
- package/dist/admin/copy/goals.d.ts +21 -6
- package/dist/admin/copy/goals.js +27 -6
- package/dist/admin/copy/readiness.d.ts +22 -0
- package/dist/admin/copy/readiness.js +21 -0
- package/dist/admin/copy/recommendation.d.ts +0 -3
- package/dist/admin/copy/recommendation.js +0 -3
- package/dist/admin/copy/results.d.ts +43 -2
- package/dist/admin/copy/results.js +44 -3
- package/dist/admin/copy/review.d.ts +2 -2
- package/dist/admin/copy/review.js +2 -2
- package/dist/admin/copy/setting.d.ts +2 -0
- package/dist/admin/copy/setting.js +2 -0
- package/dist/admin/copy/surface.d.ts +7 -5
- package/dist/admin/copy/surface.js +9 -5
- package/dist/admin/copy/testPage.d.ts +10 -0
- package/dist/admin/copy/testPage.js +10 -0
- package/dist/admin/format/date.d.ts +2 -0
- package/dist/admin/format/date.js +10 -0
- package/dist/admin/paths.d.ts +2 -1
- package/dist/admin/paths.js +2 -1
- package/dist/admin/previewLinks.model.d.ts +4 -2
- package/dist/admin/previewLinks.model.js +8 -7
- package/dist/admin/results/CiChart.js +11 -21
- package/dist/admin/results/DataQualityPanel.d.ts +4 -2
- package/dist/admin/results/DataQualityPanel.js +30 -17
- package/dist/admin/results/DataTable.d.ts +16 -0
- package/dist/admin/results/DataTable.js +152 -0
- package/dist/admin/results/StatTile.d.ts +7 -0
- package/dist/admin/results/StatTile.js +22 -0
- package/dist/admin/results/VerdictHero.d.ts +13 -0
- package/dist/admin/results/{VerdictCard.js → VerdictHero.js} +15 -12
- package/dist/admin/results/legacy.d.ts +16 -0
- package/dist/admin/results/legacy.js +27 -0
- package/dist/admin/results/releasePages.model.d.ts +17 -0
- package/dist/admin/results/releasePages.model.js +10 -0
- package/dist/admin/results/results.css +64 -0
- package/dist/admin/results/results.model.d.ts +11 -7
- package/dist/admin/results/results.model.js +24 -7
- package/dist/admin/results/snapshot.d.ts +14 -0
- package/dist/admin/results/snapshot.js +3 -0
- package/dist/admin/surface/ResultsRoute.d.ts +1 -1
- package/dist/admin/surface/ResultsRoute.js +59 -34
- package/dist/admin/surface/SurfaceShell.d.ts +11 -2
- package/dist/admin/surface/SurfaceShell.js +49 -14
- package/dist/admin/surface/TestsHome.d.ts +6 -1
- package/dist/admin/surface/TestsHome.js +182 -114
- package/dist/admin/surface/TestsSurface.js +56 -16
- package/dist/admin/surface/goals/GoalForm.js +341 -308
- package/dist/admin/surface/goals/GoalsList.js +123 -89
- package/dist/admin/surface/goals/data.d.ts +8 -2
- package/dist/admin/surface/goals/data.js +24 -16
- package/dist/admin/surface/goals/goals.css +10 -13
- package/dist/admin/surface/home.model.d.ts +8 -8
- package/dist/admin/surface/home.model.js +23 -12
- package/dist/admin/surface/newTest/NewTest.js +9 -9
- package/dist/admin/surface/pageBlock/parts.js +31 -26
- package/dist/admin/surface/readiness/ActionDialogs.js +47 -19
- package/dist/admin/surface/readiness/Banner.d.ts +3 -0
- package/dist/admin/surface/readiness/Banner.js +58 -33
- package/dist/admin/surface/readiness/SaveStatus.d.ts +1 -0
- package/dist/admin/surface/readiness/SaveStatus.js +6 -3
- package/dist/admin/surface/readiness/readiness.model.d.ts +2 -0
- package/dist/admin/surface/readiness/readiness.model.js +25 -9
- package/dist/admin/surface/readiness/useTransition.d.ts +1 -0
- package/dist/admin/surface/readiness/useTransition.js +2 -1
- package/dist/admin/surface/results/ReleasePages.d.ts +14 -0
- package/dist/admin/surface/results/ReleasePages.js +116 -0
- package/dist/admin/surface/results/ResultsBody.d.ts +11 -1
- package/dist/admin/surface/results/ResultsBody.js +277 -105
- package/dist/admin/surface/results/ResultsHeader.d.ts +10 -0
- package/dist/admin/surface/results/ResultsHeader.js +177 -0
- package/dist/admin/surface/results/recommendation.model.d.ts +3 -2
- package/dist/admin/surface/results/recommendation.model.js +12 -9
- package/dist/admin/surface/results/useResults.d.ts +21 -3
- package/dist/admin/surface/results/useResults.js +2 -1
- package/dist/admin/surface/setting/parts.js +75 -88
- package/dist/admin/surface/setting/setting.css +3 -15
- package/dist/admin/surface/surface.css +61 -47
- package/dist/admin/surface/test/AdvancedSection.js +55 -48
- package/dist/admin/surface/test/GoalSection.js +46 -47
- package/dist/admin/surface/test/LengthSection.js +46 -34
- package/dist/admin/surface/test/Rail.d.ts +4 -0
- package/dist/admin/surface/test/Rail.js +83 -0
- package/dist/admin/surface/test/ReadinessBar.d.ts +5 -0
- package/dist/admin/surface/test/ReadinessBar.js +33 -0
- package/dist/admin/surface/test/Section.d.ts +1 -1
- package/dist/admin/surface/test/Section.js +36 -16
- package/dist/admin/surface/test/TargetingSection.js +168 -142
- package/dist/admin/surface/test/TestPage.d.ts +2 -1
- package/dist/admin/surface/test/TestPage.js +146 -68
- package/dist/admin/surface/test/VersionsSection.js +151 -120
- package/dist/admin/surface/test/fields.d.ts +11 -4
- package/dist/admin/surface/test/fields.js +84 -85
- package/dist/admin/surface/test/jumpToSection.d.ts +3 -0
- package/dist/admin/surface/test/jumpToSection.js +10 -0
- package/dist/admin/surface/test/pageParts.js +9 -3
- package/dist/admin/surface/test/testPage.css +81 -14
- package/dist/admin/surface/test/testPage.model.d.ts +2 -3
- package/dist/admin/surface/test/testPage.model.js +1 -21
- package/dist/admin/surface/useTestDocument.js +12 -5
- package/dist/admin/targeting/targeting.model.d.ts +1 -3
- package/dist/admin/targeting/targeting.model.js +3 -8
- package/dist/admin/theme.css +115 -36
- package/dist/admin/theme.d.ts +38 -0
- package/dist/admin/theme.js +38 -1
- package/dist/admin/useApi.d.ts +17 -2
- package/dist/admin/useLayerUsage.js +14 -23
- package/dist/admin/views/ExperimentsRootView.js +12 -2
- package/dist/admin/views/TestsNavLinkServer.d.ts +2 -0
- package/dist/admin/views/TestsNavLinkServer.js +43 -0
- package/dist/arms/copy.d.ts +6 -0
- package/dist/arms/copy.js +16 -0
- package/dist/arms/model.d.ts +23 -0
- package/dist/arms/model.js +173 -0
- package/dist/collections/experiments/config.js +25 -0
- package/dist/collections/experiments/lockHook.js +3 -1
- package/dist/collections/identities.js +6 -1
- package/dist/collections/metrics.js +7 -0
- package/dist/collections/metricsGuards.d.ts +9 -0
- package/dist/collections/metricsGuards.js +33 -0
- package/dist/contentDone.d.ts +4 -0
- package/dist/contentDone.js +5 -0
- package/dist/db/appends.d.ts +6 -2
- package/dist/db/appends.js +10 -4
- package/dist/db/identities.d.ts +8 -7
- package/dist/db/identities.js +9 -6
- package/dist/db/locks.d.ts +6 -0
- package/dist/db/locks.js +13 -1
- package/dist/db/metricUsage.d.ts +19 -0
- package/dist/db/metricUsage.js +54 -0
- package/dist/db/names.d.ts +5 -0
- package/dist/db/names.js +5 -0
- package/dist/db/pageOccupancy.d.ts +12 -0
- package/dist/db/pageOccupancy.js +45 -0
- package/dist/db/publishedContent.d.ts +10 -0
- package/dist/db/publishedContent.js +43 -0
- package/dist/db/results.d.ts +2 -0
- package/dist/db/results.js +6 -2
- package/dist/db/snapshots.d.ts +18 -6
- package/dist/db/snapshots.js +25 -8
- package/dist/db/sweep.js +11 -6
- package/dist/endpoints/index.js +19 -1
- package/dist/endpoints/results.js +5 -0
- package/dist/endpoints/sanitizeReadinessDetail.d.ts +1 -0
- package/dist/endpoints/sanitizeReadinessDetail.js +2 -0
- package/dist/endpoints/summary.js +20 -2
- package/dist/endpoints/transitions.d.ts +15 -0
- package/dist/endpoints/transitions.js +36 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +28 -0
- package/dist/exports/adminRsc.d.ts +1 -0
- package/dist/exports/adminRsc.js +1 -0
- package/dist/exports/index.d.ts +2 -1
- package/dist/exports/index.js +1 -0
- package/dist/exports/puck.d.ts +2 -1
- package/dist/exports/puck.js +1 -1
- package/dist/exports/puckEditor.d.ts +4 -1
- package/dist/exports/puckEditor.js +3 -1
- package/dist/exports/rsc.d.ts +6 -1
- package/dist/exports/rsc.js +2 -0
- package/dist/hooks/pageGuard.d.ts +5 -0
- package/dist/hooks/pageGuard.js +139 -0
- package/dist/lib/fetchAllDocs.d.ts +16 -0
- package/dist/lib/fetchAllDocs.js +56 -0
- package/dist/log.js +6 -3
- package/dist/manifest/compile.d.ts +5 -0
- package/dist/manifest/compile.js +5 -1
- package/dist/options.js +16 -0
- package/dist/plugin.js +30 -2
- package/dist/puck/ExperimentPicker.js +47 -16
- package/dist/puck/ExperimentSlot.d.ts +51 -1
- package/dist/puck/ExperimentSlot.js +23 -12
- package/dist/puck/copy.d.ts +6 -5
- package/dist/puck/copy.js +6 -5
- package/dist/puck/editor/CreateExperimentDrawer.js +46 -56
- package/dist/puck/editor/ExperimentSlotEditor.d.ts +52 -1
- package/dist/puck/editor/ExperimentSlotEditor.js +292 -137
- package/dist/puck/editor/ResultsReadout.d.ts +6 -0
- package/dist/puck/editor/ResultsReadout.js +99 -0
- package/dist/puck/editor/VersionBuilder.d.ts +34 -0
- package/dist/puck/editor/VersionBuilder.js +317 -0
- package/dist/puck/editor/armWrites.d.ts +7 -0
- package/dist/puck/editor/armWrites.js +71 -0
- package/dist/puck/editor/armsCache.d.ts +14 -0
- package/dist/puck/editor/armsCache.js +78 -0
- package/dist/puck/editor/copy.d.ts +33 -1
- package/dist/puck/editor/copy.js +45 -8
- package/dist/puck/editor/createTest.d.ts +1 -1
- package/dist/puck/editor/createTest.js +25 -14
- package/dist/puck/editor/editor.chrome.css +121 -0
- package/dist/puck/editor/editor.css +288 -204
- package/dist/puck/editor/editor.model.d.ts +4 -1
- package/dist/puck/editor/editor.model.js +10 -7
- package/dist/puck/editor/experimentOutlinePlugin.d.ts +3 -0
- package/dist/puck/editor/experimentOutlinePlugin.js +89 -0
- package/dist/puck/experimentSlotField.d.ts +2 -0
- package/dist/puck/experimentSlotField.js +11 -3
- package/dist/puck/hostContext.d.ts +10 -0
- package/dist/puck/hostContext.js +12 -0
- package/dist/puck/routes.d.ts +12 -0
- package/dist/puck/routes.js +11 -0
- package/dist/puck/slotConfig.d.ts +1 -1
- package/dist/puck/slotConfig.js +2 -2
- package/dist/results/aggregate.js +4 -3
- package/dist/results/analyse.d.ts +4 -1
- package/dist/results/analyse.js +27 -11
- package/dist/results/cuped.js +3 -2
- package/dist/results/identityCredit.d.ts +15 -7
- package/dist/results/identityCredit.js +32 -33
- package/dist/results/rolloutUnits.js +2 -1
- package/dist/results/snapshot.js +8 -2
- package/dist/results/srm.js +2 -1
- package/dist/sanitizeReadinessDetail.d.ts +7 -0
- package/dist/sanitizeReadinessDetail.js +63 -0
- package/dist/server/linkIdentity.d.ts +10 -1
- package/dist/server/linkIdentity.js +17 -15
- package/dist/server/metadata.d.ts +5 -0
- package/dist/server/metadata.js +18 -3
- package/dist/server/normaliseContentRelationships.d.ts +7 -0
- package/dist/server/normaliseContentRelationships.js +62 -0
- package/dist/server/recordContentChange.d.ts +1 -1
- package/dist/server/recordContentChange.js +17 -8
- package/dist/server/runResults.d.ts +3 -2
- package/dist/server/runResults.js +157 -35
- package/dist/targeting/values.d.ts +4 -0
- package/dist/targeting/values.js +10 -0
- package/dist/transitions/addMetric.js +8 -0
- package/dist/transitions/archive.js +2 -3
- package/dist/transitions/checkStart.d.ts +10 -3
- package/dist/transitions/checkStart.js +25 -0
- package/dist/transitions/contentFields.d.ts +7 -0
- package/dist/transitions/contentFields.js +20 -0
- package/dist/transitions/decide.js +22 -2
- package/dist/transitions/dismissContentEntry.d.ts +8 -0
- package/dist/transitions/dismissContentEntry.js +45 -0
- package/dist/transitions/makeContent.d.ts +5 -2
- package/dist/transitions/makeContent.js +217 -118
- package/dist/transitions/phaseRecord.d.ts +8 -2
- package/dist/transitions/placements.d.ts +3 -6
- package/dist/transitions/placements.js +12 -1
- package/dist/transitions/preflightData.d.ts +9 -1
- package/dist/transitions/preflightData.js +138 -20
- package/dist/transitions/releasePages.d.ts +8 -0
- package/dist/transitions/releasePages.js +26 -0
- package/dist/transitions/restoreContentEntry.d.ts +8 -0
- package/dist/transitions/restoreContentEntry.js +47 -0
- package/dist/transitions/start.js +62 -7
- package/dist/types/contracts.d.ts +24 -4
- package/dist/types/endpoints.d.ts +34 -3
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/reasons.d.ts +3 -0
- package/dist/types/reasons.js +1 -0
- package/dist/types/snapshot.d.ts +6 -1
- package/dist/ui/Banner.d.ts +2 -0
- package/dist/ui/Banner.js +2 -0
- package/dist/ui/Breadcrumb.d.ts +7 -0
- package/dist/ui/Breadcrumb.js +25 -0
- package/dist/ui/Button.d.ts +15 -0
- package/dist/ui/Button.js +32 -0
- package/dist/ui/Card.d.ts +12 -0
- package/dist/ui/Card.js +40 -0
- package/dist/ui/Checkbox.d.ts +2 -0
- package/dist/ui/Checkbox.js +2 -0
- package/dist/{admin/components → ui}/ChipsInput.d.ts +4 -3
- package/dist/{admin/components → ui}/ChipsInput.js +20 -12
- package/dist/ui/Dialog.d.ts +13 -0
- package/dist/ui/Dialog.js +85 -0
- package/dist/ui/EmptyState.d.ts +2 -0
- package/dist/ui/EmptyState.js +2 -0
- package/dist/ui/Feedback.d.ts +24 -0
- package/dist/ui/Feedback.js +40 -0
- package/dist/ui/Inputs.d.ts +34 -0
- package/dist/ui/Inputs.js +225 -0
- package/dist/ui/NumberInput.d.ts +2 -0
- package/dist/ui/NumberInput.js +2 -0
- package/dist/ui/Range.d.ts +2 -0
- package/dist/ui/Range.js +2 -0
- package/dist/ui/SearchInput.d.ts +2 -0
- package/dist/ui/SearchInput.js +2 -0
- package/dist/ui/SectionCard.d.ts +2 -0
- package/dist/ui/SectionCard.js +2 -0
- package/dist/ui/Select.d.ts +2 -0
- package/dist/ui/Select.js +2 -0
- package/dist/ui/StatusChip.d.ts +2 -0
- package/dist/ui/StatusChip.js +2 -0
- package/dist/ui/Tabs.d.ts +16 -0
- package/dist/ui/Tabs.js +61 -0
- package/dist/ui/TextInput.d.ts +2 -0
- package/dist/ui/TextInput.js +2 -0
- package/dist/ui/Textarea.d.ts +2 -0
- package/dist/ui/Textarea.js +2 -0
- package/dist/ui/Toggle.d.ts +2 -0
- package/dist/ui/Toggle.js +2 -0
- package/dist/ui/accent.d.ts +40 -0
- package/dist/ui/accent.js +139 -0
- package/dist/ui/index.d.ts +9 -0
- package/dist/ui/index.js +9 -0
- package/dist/ui/ui.css +92 -0
- package/package.json +5 -2
- package/dist/admin/components/chipsInput.css +0 -11
- package/dist/admin/results/ArmTable.d.ts +0 -8
- package/dist/admin/results/ArmTable.js +0 -113
- package/dist/admin/results/VerdictCard.d.ts +0 -9
- package/dist/admin/surface/goals/GoalsBreadcrumb.d.ts +0 -5
- package/dist/admin/surface/goals/GoalsBreadcrumb.js +0 -47
- package/dist/admin/surface/test/TestHeader.d.ts +0 -4
- package/dist/admin/surface/test/TestHeader.js +0 -70
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { COLUMNS, TABLES } from './names.js';
|
|
2
|
+
import { qualifiedTable, quoteIdentifier as q } from './sql.js';
|
|
3
|
+
/** Page tests that occupy paths, with the arms the runtime routes on (`compileManifest`):
|
|
4
|
+
* - running or decided: the latest round's arms, or for a rollout the latest test round's;
|
|
5
|
+
* - archived with a standing result (winner, not discarded, pages not released): the latest test
|
|
6
|
+
* round's arms whose paths are rewritten to the winner (archivedPages), so a new test cannot
|
|
7
|
+
* silently take them over.
|
|
8
|
+
* Documents' arms may have been edited while stopped, so occupancy never reads them. */ export async function routedPageTests(exec) {
|
|
9
|
+
const e = COLUMNS.experiments, p = COLUMNS.phases;
|
|
10
|
+
const experiments = qualifiedTable(exec, TABLES.experiments), phases = qualifiedTable(exec, TABLES.phases);
|
|
11
|
+
const testRound = `LEFT JOIN LATERAL (SELECT ${q(p.arms)} AS arms FROM ${phases}
|
|
12
|
+
WHERE ${q(p.experimentId)} = x.${q(e.id)} AND ${q(p.phaseKind)} = 'test' ORDER BY ${q(p.phase)} DESC LIMIT 1) test ON true`;
|
|
13
|
+
const { rows } = await exec.query(`
|
|
14
|
+
SELECT x.${q(e.id)} AS "id", CASE WHEN latest.kind = 'rollout' THEN test.arms ELSE latest.arms END AS "arms", NULL::text AS "winnerArm"
|
|
15
|
+
FROM ${experiments} x
|
|
16
|
+
JOIN LATERAL (SELECT ${q(p.phaseKind)} AS kind, ${q(p.arms)} AS arms FROM ${phases}
|
|
17
|
+
WHERE ${q(p.experimentId)} = x.${q(e.id)} ORDER BY ${q(p.phase)} DESC LIMIT 1) latest ON true
|
|
18
|
+
${testRound}
|
|
19
|
+
WHERE x.${q(e.kind)} = 'page' AND x.${q(e.status)} IN ('running', 'decided')
|
|
20
|
+
UNION ALL
|
|
21
|
+
SELECT x.${q(e.id)} AS "id", test.arms AS "arms", x.${q(e.decisionWinnerArm)} AS "winnerArm"
|
|
22
|
+
FROM ${experiments} x
|
|
23
|
+
${testRound}
|
|
24
|
+
WHERE x.${q(e.kind)} = 'page' AND x.${q(e.status)} = 'archived' AND x.${q(e.decisionWinnerArm)} IS NOT NULL
|
|
25
|
+
AND x.${q(e.decisionDiscardedAt)} IS NULL AND x.${q(e.decisionPagesReleasedAt)} IS NULL
|
|
26
|
+
ORDER BY "id"`, []);
|
|
27
|
+
const occupied = [];
|
|
28
|
+
for (const row of rows){
|
|
29
|
+
const arms = Array.isArray(row.arms) ? row.arms : [];
|
|
30
|
+
if (row.winnerArm == null) {
|
|
31
|
+
occupied.push({
|
|
32
|
+
id: row.id,
|
|
33
|
+
arms
|
|
34
|
+
});
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
// compileManifest's archivedPages rule: every arm path other than the winner's.
|
|
38
|
+
const winnerPath = arms.find((arm)=>arm.key === row.winnerArm)?.pageTarget;
|
|
39
|
+
if (winnerPath) occupied.push({
|
|
40
|
+
id: row.id,
|
|
41
|
+
arms: arms.filter((arm)=>arm.pageTarget && arm.pageTarget !== winnerPath)
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return occupied;
|
|
45
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
import type { DocId } from '../types/contracts.js';
|
|
3
|
+
/** §7.8 make-content: the published main row, so Payload's draft baseline never fills a field (KI-1). */
|
|
4
|
+
export declare function publishedContent(req: PayloadRequest, collection: string, id: DocId): Promise<(Record<string, unknown> & {
|
|
5
|
+
id: DocId;
|
|
6
|
+
}) | null>;
|
|
7
|
+
/** §7.8 make-content: the latest draft version, so restoring the draft never drops fields the manager cannot read (KI-1). */
|
|
8
|
+
export declare function latestDraftContent(req: PayloadRequest, collection: string, id: DocId): Promise<(Record<string, unknown> & {
|
|
9
|
+
id: DocId;
|
|
10
|
+
}) | undefined>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Keep this module adapter-only: the §7.8 exception does not permit Local API calls.
|
|
2
|
+
/** §7.8 make-content: the published main row, so Payload's draft baseline never fills a field (KI-1). */ export async function publishedContent(req, collection, id) {
|
|
3
|
+
return req.payload.db.findOne({
|
|
4
|
+
collection,
|
|
5
|
+
req,
|
|
6
|
+
where: {
|
|
7
|
+
id: {
|
|
8
|
+
equals: id
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
// Static allow-list marker only; adapter reads have no access layer.
|
|
12
|
+
overrideAccess: true
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/** §7.8 make-content: the latest draft version, so restoring the draft never drops fields the manager cannot read (KI-1). */ export async function latestDraftContent(req, collection, id) {
|
|
16
|
+
const { docs } = await req.payload.db.findVersions({
|
|
17
|
+
collection,
|
|
18
|
+
req,
|
|
19
|
+
limit: 1,
|
|
20
|
+
pagination: false,
|
|
21
|
+
sort: '-updatedAt',
|
|
22
|
+
where: {
|
|
23
|
+
and: [
|
|
24
|
+
{
|
|
25
|
+
parent: {
|
|
26
|
+
equals: id
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
latest: {
|
|
31
|
+
equals: true
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
// Static allow-list marker only; adapter reads have no access layer.
|
|
37
|
+
overrideAccess: true
|
|
38
|
+
});
|
|
39
|
+
return docs[0] ? {
|
|
40
|
+
...docs[0].version,
|
|
41
|
+
id
|
|
42
|
+
} : undefined;
|
|
43
|
+
}
|
package/dist/db/results.d.ts
CHANGED
package/dist/db/results.js
CHANGED
|
@@ -91,12 +91,16 @@ function phaseFilter({ experimentId, phase, endedAt }, type) {
|
|
|
91
91
|
const i = COLUMNS.identities;
|
|
92
92
|
const rows = [];
|
|
93
93
|
for(let offset = 0; offset < ids.length; offset += batchSize){
|
|
94
|
-
|
|
94
|
+
// Ownership is global to the user, including browsers outside these exposure batches.
|
|
95
|
+
const table = qualifiedTable(exec, TABLES.identities);
|
|
96
|
+
const result = await exec.query(`SELECT link.${q(i.anonymousId)} AS "anonymousId", link.${q(i.userId)} AS "userId", round(extract(epoch from link.${q(i.verifiedAt)}) * 1000)::bigint AS "verifiedAt", (SELECT round(extract(epoch from min(owner.${q(i.verifiedAt)})) * 1000)::bigint FROM ${table} owner WHERE owner.${q(i.userId)} = link.${q(i.userId)}) AS "ownedAt" FROM ${table} link WHERE link.${q(i.anonymousId)} = ANY($1::text[]) ORDER BY link.${q(i.anonymousId)}`, [
|
|
95
97
|
ids.slice(offset, offset + batchSize)
|
|
96
98
|
]);
|
|
97
99
|
rows.push(...result.rows.map((row)=>({
|
|
98
100
|
anonymousId: row.anonymousId,
|
|
99
|
-
userId: String(row.userId)
|
|
101
|
+
userId: String(row.userId),
|
|
102
|
+
verifiedAt: row.verifiedAt == null ? null : Number(row.verifiedAt),
|
|
103
|
+
ownedAt: row.ownedAt == null ? null : Number(row.ownedAt)
|
|
100
104
|
})));
|
|
101
105
|
}
|
|
102
106
|
return rows;
|
package/dist/db/snapshots.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { Payload } from 'payload';
|
|
2
|
-
import type { DocId, UnitType } from '../types/contracts.js';
|
|
2
|
+
import type { Arm, DocId, ExperimentKind, Status, UnitType } from '../types/contracts.js';
|
|
3
3
|
import type { Snapshot } from '../types/snapshot.js';
|
|
4
4
|
import type { AnalysisPhase, FrozenMetric } from '../results/analyse.js';
|
|
5
5
|
import { type Executor } from './executor.js';
|
|
6
6
|
export interface ResultsPhase extends AnalysisPhase {
|
|
7
|
+
arms: Array<AnalysisPhase['arms'][number] & Pick<Arm, 'pageTarget'>>;
|
|
7
8
|
seed: string;
|
|
8
9
|
allocation: number;
|
|
9
10
|
metricDefinitions: Array<FrozenMetric & {
|
|
@@ -14,11 +15,18 @@ export interface ResultsPhase extends AnalysisPhase {
|
|
|
14
15
|
}
|
|
15
16
|
export interface ResultsCandidate {
|
|
16
17
|
phase: ResultsPhase;
|
|
18
|
+
frozenSnapshot?: Snapshot | null;
|
|
19
|
+
kind?: ExperimentKind;
|
|
20
|
+
currentPhase?: number;
|
|
21
|
+
winnerArm?: string | null;
|
|
17
22
|
unitType: UnitType;
|
|
18
23
|
status: 'running' | 'stopped' | 'decided' | 'archived';
|
|
19
24
|
latestComputedAt: string | null;
|
|
20
25
|
testArms: Array<{
|
|
26
|
+
key: string;
|
|
21
27
|
weight: number;
|
|
28
|
+
kind?: string;
|
|
29
|
+
pageTarget?: string;
|
|
22
30
|
}>;
|
|
23
31
|
layerSlug: string | null;
|
|
24
32
|
layerSlices: number[] | null;
|
|
@@ -39,10 +47,14 @@ export declare function writeSnapshots(exec: Executor, snapshots: Snapshot[]): P
|
|
|
39
47
|
/** Fetch pointers only, then the endpoint reads the bounded set through Payload
|
|
40
48
|
* with req/overrideAccess:false. Called behind the readResults/manage gate. */
|
|
41
49
|
export declare function newestSnapshotIds(exec: Executor, ids: DocId[], per: 'phase' | 'experiment'): Promise<DocId[]>;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
status:
|
|
50
|
+
type ResultsMetadata = {
|
|
51
|
+
name: string | null;
|
|
52
|
+
kind: ExperimentKind;
|
|
53
|
+
status: Status;
|
|
46
54
|
snapshotId: DocId | null;
|
|
47
55
|
winnerArm: string | null;
|
|
48
|
-
}
|
|
56
|
+
};
|
|
57
|
+
/** Called only behind the endpoint's readResults gate. Experiment collection
|
|
58
|
+
* reads require manage, so read only the identity and decision metadata needed by results. */
|
|
59
|
+
export declare function resultsMetadata(exec: Executor, id: DocId): Promise<ResultsMetadata | null>;
|
|
60
|
+
export {};
|
package/dist/db/snapshots.js
CHANGED
|
@@ -31,16 +31,33 @@ const LATE_BEACON_GRACE_MS = 24 * 60 * 60 * 1000;
|
|
|
31
31
|
'startedAt', p.${q(p.startedAt)}, 'endedAt', p.${q(p.endedAt)}, 'seed', p.${q(p.seed)},
|
|
32
32
|
'arms', p.${q(p.arms)}, 'allocation', p.${q(p.allocation)}, 'metricDefinitions', p.${q(p.metricDefinitions)},
|
|
33
33
|
'analysis', p.${q(p.analysis)}, 'contentChanges', p.${q(p.contentChanges)}) AS "phase",
|
|
34
|
-
e.${q(e.unitType)} AS "unitType", e.${q(e.status)} AS "status",
|
|
35
|
-
|
|
34
|
+
e.${q(e.kind)} AS "kind", e.${q(e.phase)} AS "currentPhase", e.${q(e.unitType)} AS "unitType", e.${q(e.status)} AS "status", e.${q(e.decisionWinnerArm)} AS "winnerArm",
|
|
35
|
+
latest."computedAt" AS "latestComputedAt",
|
|
36
36
|
(SELECT t.${q(p.arms)} FROM ${phases} t WHERE t.${q(p.experimentId)} = p.${q(p.experimentId)} AND t.${q(p.phaseKind)} = 'test' AND t.${q(p.phase)} <= p.${q(p.phase)} ORDER BY t.${q(p.phase)} DESC LIMIT 1) AS "testArms",
|
|
37
|
+
frozen."snapshot" AS "frozenSnapshot",
|
|
37
38
|
l.${q(l.slug)} AS "layerSlug", e.${q(e.layerSlices)} AS "layerSlices"
|
|
38
39
|
FROM ${phases} p JOIN ${qualifiedTable(exec, TABLES.experiments)} e ON e.${q(e.id)} = p.${q(p.experimentId)}
|
|
39
40
|
LEFT JOIN ${qualifiedTable(exec, TABLES.layers)} l ON l.${q(l.id)} = e.${q(e.layerId)}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
LEFT JOIN LATERAL (SELECT max(s.${q(s.computedAt)}) AS "computedAt" FROM ${snapshots} s
|
|
42
|
+
WHERE s.${q(s.experimentId)} = p.${q(p.experimentId)} AND s.${q(s.phase)} = p.${q(p.phase)}) latest ON true
|
|
43
|
+
LEFT JOIN LATERAL (SELECT s.${q(s.snapshot)} AS "snapshot" FROM ${snapshots} s
|
|
44
|
+
WHERE s.${q(s.experimentId)} = p.${q(p.experimentId)} AND s.${q(s.phase)} = p.${q(p.phase)}
|
|
45
|
+
AND p.${q(p.endedAt)} IS NOT NULL
|
|
46
|
+
AND (s.${q(s.computedAt)} > p.${q(p.endedAt)} + $2 * interval '1 millisecond' OR p.${q(p.endedAt)} < $1)
|
|
47
|
+
ORDER BY s.${q(s.computedAt)} DESC, s.${q(s.id)} DESC LIMIT 1) frozen ON true
|
|
48
|
+
WHERE ((e.${q(e.kind)} = 'page'
|
|
49
|
+
AND (p.${q(p.endedAt)} IS NULL OR p.${q(p.endedAt)} >= $1 OR EXISTS
|
|
50
|
+
(SELECT 1 FROM ${snapshots} existing WHERE existing.${q(s.experimentId)} = p.${q(p.experimentId)} AND existing.${q(s.phase)} = p.${q(p.phase)}))
|
|
51
|
+
AND e.${q(e.status)} IN ('running', 'decided') AND (p.${q(p.phase)} = e.${q(e.phase)}
|
|
52
|
+
OR p.${q(p.phase)} = (SELECT max(active.${q(p.phase)}) FROM ${phases} active WHERE active.${q(p.experimentId)} = e.${q(e.id)} AND active.${q(p.phaseKind)} = 'test')))
|
|
53
|
+
OR ((p.${q(p.endedAt)} IS NULL OR p.${q(p.endedAt)} >= $1)
|
|
54
|
+
AND NOT EXISTS (SELECT 1 FROM ${snapshots} done WHERE done.${q(s.experimentId)} = p.${q(p.experimentId)} AND done.${q(s.phase)} = p.${q(p.phase)}
|
|
55
|
+
AND done.${q(s.computedAt)} > p.${q(p.endedAt)} + $2 * interval '1 millisecond')))
|
|
56
|
+
${experimentId === undefined ? '' : ` AND p.${q(p.experimentId)} = $3`}
|
|
57
|
+
ORDER BY (frozen."snapshot" IS NOT NULL),
|
|
58
|
+
CASE WHEN frozen."snapshot" IS NULL THEN latest."computedAt" END NULLS FIRST,
|
|
59
|
+
CASE WHEN frozen."snapshot" IS NOT NULL THEN random() END,
|
|
60
|
+
p.${q(p.experimentId)}, p.${q(p.phase)}`, params);
|
|
44
61
|
return rows;
|
|
45
62
|
}
|
|
46
63
|
export async function writeSnapshot(exec, snapshot) {
|
|
@@ -80,9 +97,9 @@ export async function writeSnapshot(exec, snapshot) {
|
|
|
80
97
|
return rows.map((row)=>row.id);
|
|
81
98
|
}
|
|
82
99
|
/** Called only behind the endpoint's readResults gate. Experiment collection
|
|
83
|
-
* reads require manage, so read only the decision metadata needed by results. */ export async function resultsMetadata(exec, id) {
|
|
100
|
+
* reads require manage, so read only the identity and decision metadata needed by results. */ export async function resultsMetadata(exec, id) {
|
|
84
101
|
const c = COLUMNS.experiments;
|
|
85
|
-
const { rows } = await exec.query(`SELECT ${q(c.status)} AS "status", ${q(c.decisionSnapshotId)} AS "snapshotId", ${q(c.decisionWinnerArm)} AS "winnerArm" FROM ${qualifiedTable(exec, TABLES.experiments)} WHERE ${q(c.id)} = $1`, [
|
|
102
|
+
const { rows } = await exec.query(`SELECT ${q(c.name)} AS "name", ${q(c.kind)} AS "kind", ${q(c.status)} AS "status", ${q(c.decisionSnapshotId)} AS "snapshotId", ${q(c.decisionWinnerArm)} AS "winnerArm" FROM ${qualifiedTable(exec, TABLES.experiments)} WHERE ${q(c.id)} = $1`, [
|
|
86
103
|
id
|
|
87
104
|
]);
|
|
88
105
|
return rows[0] ?? null;
|
package/dist/db/sweep.js
CHANGED
|
@@ -11,6 +11,13 @@ import { ResultsBudgetError } from './pgExecutor.js';
|
|
|
11
11
|
const events = qualifiedTable(exec, TABLES.events), phases = qualifiedTable(exec, TABLES.phases);
|
|
12
12
|
const revision = `(SELECT ${q(s.revision)} FROM ${qualifiedTable(exec, TABLES.state)} LIMIT 1)`;
|
|
13
13
|
let swept = 0;
|
|
14
|
+
const warnNoProgress = ()=>logger?.warn('Experiment retention sweep made no progress before its budget expired; eligible rows may remain.');
|
|
15
|
+
// Every budget-limited exit with nothing swept is reported, whether the budget ran out mid-statement (the catch
|
|
16
|
+
// below) or before the next batch could start (here).
|
|
17
|
+
const finish = ()=>{
|
|
18
|
+
if (swept === 0 && !shouldContinue()) warnNoProgress();
|
|
19
|
+
return swept;
|
|
20
|
+
};
|
|
14
21
|
// Each statement still uses the caller's budget/race-aware executor.
|
|
15
22
|
async function batches(predicate, order, params = []) {
|
|
16
23
|
while(shouldContinue()){
|
|
@@ -38,7 +45,7 @@ import { ResultsBudgetError } from './pgExecutor.js';
|
|
|
38
45
|
type
|
|
39
46
|
]);
|
|
40
47
|
}
|
|
41
|
-
if (!shouldContinue()) return
|
|
48
|
+
if (!shouldContinue()) return finish();
|
|
42
49
|
// Final-endedAt retention protects every phase while any phase is open.
|
|
43
50
|
const ended = await exec.query(`SELECT ${q(p.experimentId)} AS "experimentId" FROM ${phases}
|
|
44
51
|
WHERE ${q(p.experimentId)} IS NOT NULL GROUP BY ${q(p.experimentId)}
|
|
@@ -54,7 +61,7 @@ import { ResultsBudgetError } from './pgExecutor.js';
|
|
|
54
61
|
cutoff
|
|
55
62
|
]);
|
|
56
63
|
}
|
|
57
|
-
if (!shouldContinue()) return
|
|
64
|
+
if (!shouldContinue()) return finish();
|
|
58
65
|
// Every protected window ends at/after cutoff (or is open). For old
|
|
59
66
|
// conversions their union therefore starts at the earliest lookback start;
|
|
60
67
|
// everything before that single bound is safe. Compute it once, in the same
|
|
@@ -80,11 +87,9 @@ import { ResultsBudgetError } from './pgExecutor.js';
|
|
|
80
87
|
before,
|
|
81
88
|
frozen.revision
|
|
82
89
|
]);
|
|
83
|
-
return
|
|
90
|
+
return finish();
|
|
84
91
|
} catch (error) {
|
|
85
|
-
if (swept === 0 && error instanceof ResultsBudgetError)
|
|
86
|
-
logger?.warn('Experiment retention sweep made no progress before its budget expired; eligible rows may remain.');
|
|
87
|
-
}
|
|
92
|
+
if (swept === 0 && error instanceof ResultsBudgetError) warnNoProgress();
|
|
88
93
|
throw error;
|
|
89
94
|
}
|
|
90
95
|
}
|
package/dist/endpoints/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { XpNotImplemented, XpRefusal } from '../errors.js';
|
|
|
4
4
|
import { readUnitCookie } from '../runtime/cookie.js';
|
|
5
5
|
import { handleManifest } from './manifest.js';
|
|
6
6
|
import { handleEvents } from './events.js';
|
|
7
|
-
import { handleStart, handleAllocate, handleAddMetric, handleStop, handleDecide, handleReleaseHoldout, handleMakeContent, handleArchive } from './transitions.js';
|
|
7
|
+
import { handleStart, handleAllocate, handleAddMetric, handleStop, handleDecide, handleReleaseHoldout, handleReleasePages, handleMakeContent, handleDismissContentEntry, handleRestoreContentEntry, handleArchive } from './transitions.js';
|
|
8
8
|
import { handlePreflight } from './preflight.js';
|
|
9
9
|
import { handlePreviewToken } from './previewToken.js';
|
|
10
10
|
import { handleResults } from './results.js';
|
|
@@ -59,12 +59,30 @@ export const ROUTES = [
|
|
|
59
59
|
access: 'manage',
|
|
60
60
|
handler: handleReleaseHoldout
|
|
61
61
|
},
|
|
62
|
+
{
|
|
63
|
+
method: 'post',
|
|
64
|
+
path: '/xp/experiments/:id/release-pages',
|
|
65
|
+
access: 'manage',
|
|
66
|
+
handler: handleReleasePages
|
|
67
|
+
},
|
|
62
68
|
{
|
|
63
69
|
method: 'post',
|
|
64
70
|
path: '/xp/experiments/:id/make-content',
|
|
65
71
|
access: 'manage',
|
|
66
72
|
handler: handleMakeContent
|
|
67
73
|
},
|
|
74
|
+
{
|
|
75
|
+
method: 'post',
|
|
76
|
+
path: '/xp/experiments/:id/content-report/dismiss',
|
|
77
|
+
access: 'manage',
|
|
78
|
+
handler: handleDismissContentEntry
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
method: 'post',
|
|
82
|
+
path: '/xp/experiments/:id/content-report/restore',
|
|
83
|
+
access: 'manage',
|
|
84
|
+
handler: handleRestoreContentEntry
|
|
85
|
+
},
|
|
68
86
|
{
|
|
69
87
|
method: 'post',
|
|
70
88
|
path: '/xp/experiments/:id/archive',
|
|
@@ -111,6 +111,11 @@ export async function handleResults(req, _ctx) {
|
|
|
111
111
|
if (selected !== null && !records.some((record)=>record.phase === phase)) return resultsError(404, 'not-found', 'Round not found');
|
|
112
112
|
const snapshot = snapshots.find((snapshot)=>snapshot.phase === phase)?.snapshot ?? null;
|
|
113
113
|
const response = {
|
|
114
|
+
experiment: {
|
|
115
|
+
name: metadata.name ?? '',
|
|
116
|
+
kind: metadata.kind,
|
|
117
|
+
status: metadata.status
|
|
118
|
+
},
|
|
114
119
|
phases,
|
|
115
120
|
snapshot,
|
|
116
121
|
...decision ? {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { sanitizeReadinessDetail } from '../sanitizeReadinessDetail.js';
|
|
@@ -2,6 +2,7 @@ import { checkStart } from '../transitions/checkStart.js';
|
|
|
2
2
|
import { loadStartInputsBatch } from '../transitions/preflightData.js';
|
|
3
3
|
import { readSnapshots, resultsError } from './results.js';
|
|
4
4
|
import { experimentId } from './routeId.js';
|
|
5
|
+
import { sanitizeReadinessDetail } from './sanitizeReadinessDetail.js';
|
|
5
6
|
export async function handleSummary(req, { options }) {
|
|
6
7
|
let ids;
|
|
7
8
|
try {
|
|
@@ -40,7 +41,13 @@ export async function handleSummary(req, { options }) {
|
|
|
40
41
|
verdict: {
|
|
41
42
|
code: snapshot.verdict.code,
|
|
42
43
|
headline: snapshot.verdict.headline
|
|
43
|
-
}
|
|
44
|
+
},
|
|
45
|
+
...snapshot.health ? {
|
|
46
|
+
health: snapshot.health.slice(0, 100).map(({ code, path })=>({
|
|
47
|
+
code,
|
|
48
|
+
path: path.slice(0, 128)
|
|
49
|
+
}))
|
|
50
|
+
} : {}
|
|
44
51
|
} : {
|
|
45
52
|
exposures: 0,
|
|
46
53
|
days: 0,
|
|
@@ -72,7 +79,18 @@ export async function handleSummary(req, { options }) {
|
|
|
72
79
|
if (!experiment) continue;
|
|
73
80
|
const input = inputs.get(String(experiment.id));
|
|
74
81
|
try {
|
|
75
|
-
|
|
82
|
+
const checks = input ? checkStart(input) : [];
|
|
83
|
+
response[key].ready = !!input && checks.every((check)=>check.ok);
|
|
84
|
+
const next = checks.find((check)=>!check.ok && !check.warning);
|
|
85
|
+
if (next) {
|
|
86
|
+
const detail = sanitizeReadinessDetail(next.code, next.detail);
|
|
87
|
+
response[key].next = {
|
|
88
|
+
code: next.code,
|
|
89
|
+
...detail === undefined ? {} : {
|
|
90
|
+
detail
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
76
94
|
} catch {
|
|
77
95
|
response[key].ready = false;
|
|
78
96
|
}
|
|
@@ -35,6 +35,21 @@ export declare function handleMakeContent<TUser extends {
|
|
|
35
35
|
} = {
|
|
36
36
|
id: string | number;
|
|
37
37
|
}>(req: PayloadRequest, ctx: EndpointContext<TUser>): Promise<Response>;
|
|
38
|
+
export declare function handleReleasePages<TUser extends {
|
|
39
|
+
id: string | number;
|
|
40
|
+
} = {
|
|
41
|
+
id: string | number;
|
|
42
|
+
}>(req: PayloadRequest, _ctx: EndpointContext<TUser>): Promise<Response>;
|
|
43
|
+
export declare function handleDismissContentEntry<TUser extends {
|
|
44
|
+
id: string | number;
|
|
45
|
+
} = {
|
|
46
|
+
id: string | number;
|
|
47
|
+
}>(req: PayloadRequest, _ctx: EndpointContext<TUser>): Promise<Response>;
|
|
48
|
+
export declare function handleRestoreContentEntry<TUser extends {
|
|
49
|
+
id: string | number;
|
|
50
|
+
} = {
|
|
51
|
+
id: string | number;
|
|
52
|
+
}>(req: PayloadRequest, _ctx: EndpointContext<TUser>): Promise<Response>;
|
|
38
53
|
export declare function handleArchive<TUser extends {
|
|
39
54
|
id: string | number;
|
|
40
55
|
} = {
|
|
@@ -5,6 +5,9 @@ import { addMetric } from '../transitions/addMetric.js';
|
|
|
5
5
|
import { stop } from '../transitions/stop.js';
|
|
6
6
|
import { decide } from '../transitions/decide.js';
|
|
7
7
|
import { releaseHoldout } from '../transitions/releaseHoldout.js';
|
|
8
|
+
import { releasePages } from '../transitions/releasePages.js';
|
|
9
|
+
import { dismissContentEntry } from '../transitions/dismissContentEntry.js';
|
|
10
|
+
import { restoreContentEntry } from '../transitions/restoreContentEntry.js';
|
|
8
11
|
import { makeContent } from '../transitions/makeContent.js';
|
|
9
12
|
import { archive } from '../transitions/archive.js';
|
|
10
13
|
import { routeId } from './routeId.js';
|
|
@@ -135,6 +138,39 @@ export async function handleMakeContent(req, ctx) {
|
|
|
135
138
|
options: ctx.options
|
|
136
139
|
}));
|
|
137
140
|
}
|
|
141
|
+
export async function handleReleasePages(req, _ctx) {
|
|
142
|
+
const value = await body(req);
|
|
143
|
+
if (!value || !only(value, [])) return invalidBody();
|
|
144
|
+
return respond(req, (id)=>releasePages(req, {
|
|
145
|
+
experimentId: id
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
export async function handleDismissContentEntry(req, _ctx) {
|
|
149
|
+
const value = await body(req);
|
|
150
|
+
if (!value || !only(value, [
|
|
151
|
+
'collection',
|
|
152
|
+
'id'
|
|
153
|
+
]) || typeof value.collection !== 'string' || !value.collection.trim() || !(typeof value.id === 'string' && value.id.trim() || typeof value.id === 'number' && Number.isSafeInteger(value.id))) return invalidBody();
|
|
154
|
+
const collection = value.collection, id = value.id;
|
|
155
|
+
return respond(req, (experimentId)=>dismissContentEntry(req, {
|
|
156
|
+
experimentId,
|
|
157
|
+
collection,
|
|
158
|
+
id
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
export async function handleRestoreContentEntry(req, _ctx) {
|
|
162
|
+
const value = await body(req);
|
|
163
|
+
if (!value || !only(value, [
|
|
164
|
+
'collection',
|
|
165
|
+
'id'
|
|
166
|
+
]) || typeof value.collection !== 'string' || !value.collection.trim() || !(typeof value.id === 'string' && value.id.trim() || typeof value.id === 'number' && Number.isSafeInteger(value.id))) return invalidBody();
|
|
167
|
+
const collection = value.collection, id = value.id;
|
|
168
|
+
return respond(req, (experimentId)=>restoreContentEntry(req, {
|
|
169
|
+
experimentId,
|
|
170
|
+
collection,
|
|
171
|
+
id
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
138
174
|
export async function handleArchive(req, _ctx) {
|
|
139
175
|
const value = await body(req);
|
|
140
176
|
if (!value || !only(value, [
|
package/dist/errors.d.ts
CHANGED
|
@@ -25,3 +25,11 @@ export declare class XpRefusal extends Error {
|
|
|
25
25
|
readonly isPublic = true;
|
|
26
26
|
constructor(code: RefusalCode, message: string, detail: unknown | undefined, status: 409 | 422 | 500 | 404);
|
|
27
27
|
}
|
|
28
|
+
/** Fixed analysis diagnostics: callers can choose a code, never supply log text. */
|
|
29
|
+
export type AnalysisErrorCode = 'no-baseline' | 'primary-metric-count' | 'duplicate-metric' | 'invalid-timestamp' | 'window-hours' | 'window-before-start' | 'unknown-arm' | 'metric-not-supplied' | 'non-finite-values' | 'non-finite-covariates' | 'source-unavailable' | 'unit-type-unavailable' | 'srm-weights' | 'cuped-non-finite' | 'cuped-binomial' | 'seed-format' | 'snapshot-shape' | 'snapshot-invariants';
|
|
30
|
+
export declare const ANALYSIS_ERROR_MESSAGES: Readonly<Record<AnalysisErrorCode, string>>;
|
|
31
|
+
export declare class XpAnalysisError extends Error {
|
|
32
|
+
name: string;
|
|
33
|
+
readonly code: AnalysisErrorCode;
|
|
34
|
+
constructor(code: AnalysisErrorCode);
|
|
35
|
+
}
|
package/dist/errors.js
CHANGED
|
@@ -31,3 +31,31 @@ export class XpRefusal extends Error {
|
|
|
31
31
|
super(message), this.code = code, this.detail = detail, this.status = status;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
export const ANALYSIS_ERROR_MESSAGES = Object.freeze({
|
|
35
|
+
'no-baseline': 'Phase comparison baseline or shipped arm is missing',
|
|
36
|
+
'primary-metric-count': 'Phase must have exactly one primary metric',
|
|
37
|
+
'duplicate-metric': 'Duplicate metric id',
|
|
38
|
+
'invalid-timestamp': 'Event or exposure timestamp is invalid',
|
|
39
|
+
'window-hours': 'Attribution window must be a finite number of hours greater than 0',
|
|
40
|
+
'window-before-start': 'Analysis window ends before the phase starts',
|
|
41
|
+
'unknown-arm': 'Exposed unit has an unknown arm',
|
|
42
|
+
'metric-not-supplied': 'Metric data was not supplied',
|
|
43
|
+
'non-finite-values': 'Metric values must be finite',
|
|
44
|
+
'non-finite-covariates': 'Covariates must be finite',
|
|
45
|
+
'source-unavailable': 'Metric source unavailable',
|
|
46
|
+
'unit-type-unavailable': 'Metric unit type unavailable',
|
|
47
|
+
'srm-weights': 'SRM requires positive weights',
|
|
48
|
+
'cuped-non-finite': 'Metric values and covariates must be finite',
|
|
49
|
+
'cuped-binomial': 'Binomial values and covariates must be 0 or 1',
|
|
50
|
+
'seed-format': 'Assignment seed must end in |',
|
|
51
|
+
'snapshot-shape': 'Invalid Snapshot shape (all numbers must be finite)',
|
|
52
|
+
'snapshot-invariants': 'Invalid Snapshot invariants'
|
|
53
|
+
});
|
|
54
|
+
export class XpAnalysisError extends Error {
|
|
55
|
+
name = 'XpAnalysisError';
|
|
56
|
+
code;
|
|
57
|
+
constructor(code){
|
|
58
|
+
super(ANALYSIS_ERROR_MESSAGES[code]);
|
|
59
|
+
this.code = code;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { ExperimentsRootView } from '../admin/views/ExperimentsRootView.js';
|
|
2
2
|
export { CollectionListRedirect } from '../admin/views/CollectionListRedirect.js';
|
|
3
3
|
export { CollectionEditRedirect } from '../admin/views/CollectionEditRedirect.js';
|
|
4
|
+
export { TestsNavLinkServer } from '../admin/views/TestsNavLinkServer.js';
|
package/dist/exports/adminRsc.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { ExperimentsRootView } from '../admin/views/ExperimentsRootView.js';
|
|
2
2
|
export { CollectionListRedirect } from '../admin/views/CollectionListRedirect.js';
|
|
3
3
|
export { CollectionEditRedirect } from '../admin/views/CollectionEditRedirect.js';
|
|
4
|
+
export { TestsNavLinkServer } from '../admin/views/TestsNavLinkServer.js';
|
package/dist/exports/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createExperimentsPlugin } from '../plugin.js';
|
|
2
2
|
export { defineFlagPoint, defineMetricSource } from '../define.js';
|
|
3
|
+
export { contentDone } from '../contentDone.js';
|
|
3
4
|
export { createArmSelectField } from '../fields/armSelect.js';
|
|
4
|
-
export type { SerializedFlagPoint, SerializedMetricSource, SerializedAttributeKeys, EditableField, Assignment, Assignments, FlagPointResult, ClientAssignments, RenderedExposure, DocId, Attributes, ExposurePolicy, ExperimentKind, Status, UnitType, ArmKey, ArmKind, TargetingRule, Arm, ParamField, MetricSource, FlagPoint, RateLimiter, ExperimentsAccess, ExperimentsPluginOptions, ArmIssueCode, RefusalCode, ReadinessCode, ErrorEnvelope, TransitionSuccess, AllocateBody, AddMetricBody, DecideBody, ArchiveBody, MakeContentResult, PreflightResult, PreviewTokenResult, PhaseSummary, ResultsResponse, SummaryResponse, EventBody, FlagDefault, Manifest, ManifestExperiment, ArmSlot, ExperimentRef, ExperimentSlotProps, ExperimentsProxyRuntimeOptions, ExperimentsProxyRuntime, ExperimentsServerRuntimeOptions, ExperimentsServerRuntime, VerdictCode, Snapshot, } from '../types/index.js';
|
|
5
|
+
export type { SerializedFlagPoint, SerializedMetricSource, SerializedAttributeKeys, EditableField, Assignment, Assignments, FlagPointResult, ClientAssignments, RenderedExposure, DocId, PreviewPlacement, Attributes, ExposurePolicy, ExperimentKind, Status, UnitType, ArmKey, ArmKind, TargetingRule, Arm, ParamField, MetricSource, FlagPoint, RateLimiter, ExperimentsAccess, ExperimentsPluginOptions, ExperimentsPageCollection, ArmIssueCode, RefusalCode, ReadinessCode, ErrorEnvelope, TransitionSuccess, AllocateBody, AddMetricBody, DecideBody, ArchiveBody, MakeContentResult, DismissContentEntryBody, DismissContentEntryResult, RestoreContentEntryBody, RestoreContentEntryResult, ContentReport, PreflightResult, PreviewTokenResult, PhaseSummary, ResultsResponse, SummaryResponse, EventBody, FlagDefault, Manifest, ManifestExperiment, ArmSlot, ExperimentRef, ExperimentSlotProps, ExperimentsProxyRuntimeOptions, ExperimentsProxyRuntime, ExperimentsServerRuntimeOptions, ExperimentsServerRuntime, VerdictCode, Snapshot, MetricUnavailableReason, ContentReportReason, } from '../types/index.js';
|
package/dist/exports/index.js
CHANGED
package/dist/exports/puck.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { ExperimentSlot } from '../puck/ExperimentSlot.js';
|
|
1
|
+
export { ExperimentSlot, createExperimentSlot } from '../puck/ExperimentSlot.js';
|
|
2
2
|
export { createExperimentSlotField } from '../puck/experimentSlotField.js';
|
|
3
3
|
export { EXPERIMENT_SLOT_TYPE } from '../types/puck.js';
|
|
4
4
|
export type { ArmSlot, ExperimentRef, ExperimentSlotProps } from '../types/puck.js';
|
|
5
|
+
export type { PuckRoutes } from '../puck/routes.js';
|
package/dist/exports/puck.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { ExperimentSlot } from '../puck/ExperimentSlot.js';
|
|
1
|
+
export { ExperimentSlot, createExperimentSlot } from '../puck/ExperimentSlot.js';
|
|
2
2
|
export { createExperimentSlotField } from '../puck/experimentSlotField.js';
|
|
3
3
|
export { EXPERIMENT_SLOT_TYPE } from '../types/puck.js';
|
|
@@ -1 +1,4 @@
|
|
|
1
|
-
export { ExperimentSlotEditor } from '../puck/editor/ExperimentSlotEditor.js';
|
|
1
|
+
export { ExperimentSlotEditor, createExperimentSlotEditor } from '../puck/editor/ExperimentSlotEditor.js';
|
|
2
|
+
export { experimentOutlinePlugin } from '../puck/editor/experimentOutlinePlugin.js';
|
|
3
|
+
export { deriveAccent } from '../ui/accent.js';
|
|
4
|
+
export type { PuckRoutes } from '../puck/routes.js';
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
export { ExperimentSlotEditor } from '../puck/editor/ExperimentSlotEditor.js';
|
|
2
|
+
export { ExperimentSlotEditor, createExperimentSlotEditor } from '../puck/editor/ExperimentSlotEditor.js';
|
|
3
|
+
export { experimentOutlinePlugin } from '../puck/editor/experimentOutlinePlugin.js';
|
|
4
|
+
export { deriveAccent } from '../ui/accent.js';
|
package/dist/exports/rsc.d.ts
CHANGED
|
@@ -2,12 +2,17 @@ export { getAssignments } from '../server/getAssignments.js';
|
|
|
2
2
|
export { resolveExperimentData } from '../server/resolveExperimentData.js';
|
|
3
3
|
export { recordExposures } from '../server/recordExposures.js';
|
|
4
4
|
export { getFlagPoint } from '../server/getFlagPoint.js';
|
|
5
|
-
export { linkIdentity } from '../server/linkIdentity.js';
|
|
5
|
+
export { linkIdentity, type LinkIdentityOutcome } from '../server/linkIdentity.js';
|
|
6
6
|
export { trackConversion } from '../server/trackConversion.js';
|
|
7
7
|
export { eraseUser } from '../server/eraseUser.js';
|
|
8
|
+
export type { MetricUnavailableReason, ContentReportReason } from '../types/reasons.js';
|
|
9
|
+
export type { PreviewPlacement } from '../types/contracts.js';
|
|
8
10
|
export { runResults } from '../server/runResults.js';
|
|
9
11
|
export { recordContentChange } from '../server/recordContentChange.js';
|
|
10
12
|
export { experimentSitemapFilter } from '../server/sitemap.js';
|
|
11
13
|
export { experimentMetadata } from '../server/metadata.js';
|
|
12
14
|
export { toClientAssignments } from '../server/toClientAssignments.js';
|
|
15
|
+
export { compileManifest } from '../manifest/compile.js';
|
|
16
|
+
export type { ManifestInputs, ManifestInputExperiment, ManifestPhase, ManifestCompileOptions } from '../manifest/compile.js';
|
|
17
|
+
export type { Manifest, ManifestExperiment } from '../types/manifest.js';
|
|
13
18
|
export { contentChangesBeforeHook as captureContentBaseline } from '../hooks/contentChanges.js';
|
package/dist/exports/rsc.js
CHANGED
|
@@ -10,4 +10,6 @@ export { recordContentChange } from '../server/recordContentChange.js';
|
|
|
10
10
|
export { experimentSitemapFilter } from '../server/sitemap.js';
|
|
11
11
|
export { experimentMetadata } from '../server/metadata.js';
|
|
12
12
|
export { toClientAssignments } from '../server/toClientAssignments.js';
|
|
13
|
+
// Pure manifest builder for host tests (CORE-43): the same compile transitions run, with no database access.
|
|
14
|
+
export { compileManifest } from '../manifest/compile.js';
|
|
13
15
|
export { contentChangesBeforeHook as captureContentBaseline } from '../hooks/contentChanges.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CollectionAfterOperationHook, CollectionBeforeChangeHook, CollectionBeforeDeleteHook, CollectionBeforeOperationHook } from 'payload';
|
|
2
|
+
export declare const pageGuardBeforeOperation: CollectionBeforeOperationHook;
|
|
3
|
+
export declare const pageGuardAfterOperation: CollectionAfterOperationHook;
|
|
4
|
+
export declare const pageGuardBeforeDelete: CollectionBeforeDeleteHook;
|
|
5
|
+
export declare const pageGuardBeforeChange: CollectionBeforeChangeHook;
|