@shipeasy/openapi 1.1.0 → 1.4.0
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/dist/index.js +49 -14
- package/dist/resources/experiments.d.ts +18 -30
- package/dist/resources/experiments.js +26 -8
- package/dist/resources/gates.d.ts +95 -0
- package/dist/resources/gates.js +23 -6
- package/dist/resources/index.d.ts +111 -30
- package/dist/resources/index.js +49 -14
- package/dist/schemas/experiments.d.ts +4 -20
- package/dist/schemas/experiments.js +7 -2
- package/dist/schemas/gates.d.ts +83 -1
- package/dist/schemas/gates.js +24 -6
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.js +31 -8
- package/openapi.json +1533 -236
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -108,12 +108,14 @@ var gateRuleSchema = z3.object({
|
|
|
108
108
|
"semver_gt",
|
|
109
109
|
"semver_gte",
|
|
110
110
|
"semver_lt",
|
|
111
|
-
"semver_lte"
|
|
111
|
+
"semver_lte",
|
|
112
|
+
"gate_pass",
|
|
113
|
+
"exp_in"
|
|
112
114
|
]).describe(
|
|
113
|
-
|
|
115
|
+
'Comparison operator. Equality: `eq`/`neq`. Set membership: `in`/`not_in` (value is an array). Numeric order: `gt`/`gte`/`lt`/`lte`. Semver order: `semver_gt`/`semver_gte`/`semver_lt`/`semver_lte` (value is a dotted version like `4.2.0`). String: `contains` (substring), `regex` (JS-flavour pattern, value is the pattern string). Cross-resource references (evaluated only on the worker hot path, where both KV blobs load): `gate_pass` (`value` is another gate\'s name \u2014 true when that gate passes for the same caller); `exp_in` (`attr` is a running experiment\'s name, `value` is a group name, `"$holdout"`, or `"$any"` \u2014 true when the caller is in that variant / the universe holdout / any variant).'
|
|
114
116
|
),
|
|
115
117
|
value: z3.unknown().describe(
|
|
116
|
-
|
|
118
|
+
'Operand. Shape depends on `op`: scalar for `eq/neq/gt/gte/lt/lte/contains/semver_*`; array of scalars for `in/not_in`; string pattern for `regex`; referenced gate name for `gate_pass`; group name / `"$holdout"` / `"$any"` for `exp_in`.'
|
|
117
119
|
)
|
|
118
120
|
}).describe(
|
|
119
121
|
"Single targeting predicate. A gate's `rules` array is evaluated with AND semantics; use the gatekeeper `stack` for OR / fall-through logic."
|
|
@@ -124,9 +126,17 @@ var gateNameSchema = z3.string().regex(
|
|
|
124
126
|
).max(128).describe(
|
|
125
127
|
"Stable gate key used by SDKs (`Shipeasy.checkGate(user, '<name>')`). Single segment or `folder.name`. Lowercase letters, digits, `_` or `-`; max 128 chars. Immutable after create \u2014 rename = delete + recreate."
|
|
126
128
|
);
|
|
129
|
+
var rampSchema = z3.object({
|
|
130
|
+
from: z3.number().int().min(0).max(1e4).describe("Start of the ramp in basis points (0\u201310000)."),
|
|
131
|
+
to: z3.number().int().min(0).max(1e4).describe("End of the ramp in basis points (0\u201310000)."),
|
|
132
|
+
startAt: z3.number().int().nonnegative().describe("Ramp start, epoch milliseconds (UTC)."),
|
|
133
|
+
durationMs: z3.number().int().positive().describe("Ramp length in milliseconds.")
|
|
134
|
+
}).describe(
|
|
135
|
+
"Linear rollout ramp. The effective rollout % interpolates `from`\u2192`to` over `[startAt, startAt+durationMs]`, clamped outside that window, evaluated against the wall-clock at eval time. Overrides the entry's static `rolloutPct` while present."
|
|
136
|
+
);
|
|
127
137
|
var stackedConditionSchema = z3.object({
|
|
128
138
|
id: z3.string().min(1).describe(
|
|
129
|
-
"Client-supplied stable id for the stack entry (used for React keys, audit diffs)."
|
|
139
|
+
"Client-supplied stable id for the stack entry (used for React keys, audit diffs, and as the default per-condition rollout salt)."
|
|
130
140
|
),
|
|
131
141
|
type: z3.literal("condition").describe("Discriminator. `condition` = rule-based predicate sub-gate."),
|
|
132
142
|
name: z3.string().optional().describe("Human label shown in the dashboard."),
|
|
@@ -135,9 +145,15 @@ var stackedConditionSchema = z3.object({
|
|
|
135
145
|
),
|
|
136
146
|
pass: z3.enum(["all", "any"]).optional().describe("Combinator across `rules`. `all` = AND (default), `any` = OR."),
|
|
137
147
|
rules: z3.array(gateRuleSchema).default([]).describe("Predicates evaluated under `pass`. Empty array = match nothing."),
|
|
148
|
+
rolloutPct: z3.number().int().min(0).max(1e4).optional().describe(
|
|
149
|
+
"Per-condition rollout in basis points. After the rules match, the caller is bucketed at this %; in-bucket \u2192 gate `true`, otherwise fall through to the next entry. Absent \u21D2 10000 (100%, every matching caller passes)."
|
|
150
|
+
),
|
|
151
|
+
bucketBy: z3.string().optional().describe("Attribute used to hash the caller into the per-condition bucket. Default `userID`."),
|
|
152
|
+
salt: z3.string().optional().describe("Per-condition hash salt. Defaults to the entry `id`, then the gate salt."),
|
|
153
|
+
ramp: rampSchema.optional(),
|
|
138
154
|
locked: z3.boolean().optional().describe("If `true`, dashboard hides edit controls (e.g. the trailing public-floor entry).")
|
|
139
155
|
}).describe(
|
|
140
|
-
"Rule-based sub-gate. When
|
|
156
|
+
"Rule-based sub-gate. When the rules match and the caller falls in the per-condition rollout bucket, evaluation short-circuits and the gate returns `true`; otherwise it falls through to the next entry."
|
|
141
157
|
);
|
|
142
158
|
var stackedRolloutSchema = z3.object({
|
|
143
159
|
id: z3.string().min(1).describe("Client-supplied stable id for the stack entry."),
|
|
@@ -153,9 +169,10 @@ var stackedRolloutSchema = z3.object({
|
|
|
153
169
|
salt: z3.string().optional().describe(
|
|
154
170
|
"Per-entry hash salt. Defaults to the gate's salt. Set explicitly to keep two rollouts independent or correlated across gates."
|
|
155
171
|
),
|
|
172
|
+
ramp: rampSchema.optional(),
|
|
156
173
|
locked: z3.boolean().optional().describe("If `true`, dashboard hides edit controls.")
|
|
157
174
|
}).describe(
|
|
158
|
-
"Percentage-rollout sub-gate. Hashes `bucketBy` (default `userID`) with `salt` and returns `true` for `rolloutPct/10000` fraction of callers."
|
|
175
|
+
"Percentage-rollout sub-gate. Hashes `bucketBy` (default `userID`) with `salt` and returns `true` for `rolloutPct/10000` fraction of callers (or the ramp's effective % when `ramp` is set)."
|
|
159
176
|
);
|
|
160
177
|
var stackedGateEntrySchema = z3.discriminatedUnion("type", [stackedConditionSchema, stackedRolloutSchema]).describe(
|
|
161
178
|
"One entry in the ordered gatekeeper stack. Evaluated top-to-bottom; first matching entry decides. Mix `condition` and `rollout` entries to express `internal-only` + `1% beta` + `50% public` in a single gate."
|
|
@@ -599,7 +616,12 @@ var experimentGroupSchema = z6.object({
|
|
|
599
616
|
var paramSchemaSchema = z6.record(z6.string(), z6.enum(["string", "bool", "number"])).describe(
|
|
600
617
|
"Map of param-name \u2192 scalar type. Defines the shape of `groups[].params`. Example: `{ headline: 'string', show_cta: 'bool' }`."
|
|
601
618
|
);
|
|
602
|
-
var bucketBySchema = z6.
|
|
619
|
+
var bucketBySchema = z6.string().min(1).max(128).regex(
|
|
620
|
+
/^[a-zA-Z_][a-zA-Z0-9_]*$/,
|
|
621
|
+
"Must be a user-attribute name (letters, digits, underscore; not starting with a digit)."
|
|
622
|
+
).describe(
|
|
623
|
+
"User-attribute name used as the bucketing key \u2014 e.g. `user_id` (default), `session_id`, `device_id`, or a custom attribute like `company_id` to keep a whole org on one variant. Defaults to `user_id` when omitted."
|
|
624
|
+
);
|
|
603
625
|
var experimentCreateSchema = z6.object({
|
|
604
626
|
name: experimentNameSchema,
|
|
605
627
|
description: z6.string().max(2e3).nullable().default(null).describe("Free-form description. Max 2000 chars, markdown rendered in the dashboard."),
|
|
@@ -671,7 +693,7 @@ var experimentUpdateSchema = z6.object({
|
|
|
671
693
|
);
|
|
672
694
|
var experimentStatusUpdateSchema = z6.object({
|
|
673
695
|
status: z6.enum(["draft", "running", "stopped", "archived"]).describe(
|
|
674
|
-
"Target status. Allowed transitions: `draft \u2192 running`, `running \u2192 stopped`, `stopped \u2192 archived`, `draft \u2192 archived
|
|
696
|
+
"Target status. Allowed transitions: `draft \u2192 running`, `running \u2192 stopped`, `stopped \u2192 archived`, `draft \u2192 archived`, and `archived \u2192 draft` (restore \u2014 only if the experiment never started). Restarting an archived experiment directly is not allowed; restore it to draft first, then start."
|
|
675
697
|
)
|
|
676
698
|
}).describe("Body for `POST /api/admin/experiments/{id}/status`.");
|
|
677
699
|
var experimentMetricsUpdateSchema = z6.object({
|
|
@@ -776,24 +798,29 @@ function experimentsClient(t) {
|
|
|
776
798
|
if (opts.cursor) query.cursor = opts.cursor;
|
|
777
799
|
return t.request("GET", BASE2, void 0, query);
|
|
778
800
|
}
|
|
779
|
-
async function
|
|
801
|
+
async function listAllWith(status) {
|
|
780
802
|
const out = [];
|
|
781
803
|
let cursor;
|
|
782
804
|
do {
|
|
783
|
-
const
|
|
805
|
+
const query = { limit: "500" };
|
|
806
|
+
if (cursor) query.cursor = cursor;
|
|
807
|
+
if (status) query.status = status;
|
|
808
|
+
const page = await t.request("GET", BASE2, void 0, query);
|
|
784
809
|
out.push(...page.data);
|
|
785
810
|
cursor = page.next_cursor ?? void 0;
|
|
786
811
|
} while (cursor);
|
|
787
812
|
return out;
|
|
788
813
|
}
|
|
814
|
+
async function listAll() {
|
|
815
|
+
return listAllWith();
|
|
816
|
+
}
|
|
789
817
|
async function resolve(idOrName) {
|
|
790
818
|
try {
|
|
791
819
|
return await t.request("GET", `${BASE2}/${idOrName}`);
|
|
792
820
|
} catch (err) {
|
|
793
821
|
if (!(err instanceof ApiError) || err.status !== 404) throw err;
|
|
794
822
|
}
|
|
795
|
-
const
|
|
796
|
-
const found = all.find((e) => e.name === idOrName);
|
|
823
|
+
const found = (await listAllWith()).find((e) => e.name === idOrName) ?? (await listAllWith("archived")).find((e) => e.name === idOrName);
|
|
797
824
|
if (!found) throw new ApiError(`Experiment '${idOrName}' not found`, 404);
|
|
798
825
|
return found;
|
|
799
826
|
}
|
|
@@ -816,6 +843,7 @@ function experimentsClient(t) {
|
|
|
816
843
|
start: (id) => setStatus(id, "running"),
|
|
817
844
|
stop: (id) => setStatus(id, "stopped"),
|
|
818
845
|
archive: (id) => setStatus(id, "archived"),
|
|
846
|
+
restore: (id) => setStatus(id, "draft"),
|
|
819
847
|
setMetrics: (id, input) => t.request("POST", `${BASE2}/${id}/metrics`, experimentMetricsUpdateSchema.parse(input)),
|
|
820
848
|
results: (id) => t.request("GET", `${BASE2}/${id}/results`),
|
|
821
849
|
timeseries: (id, metric) => t.request(
|
|
@@ -862,7 +890,7 @@ var experimentsResource = {
|
|
|
862
890
|
"",
|
|
863
891
|
"**Identity.** Stable `name` (a-z, 0-9, `_`/`-`, max 64 chars). Immutable after create.",
|
|
864
892
|
"",
|
|
865
|
-
"**Lifecycle.** `draft \u2192 running \u2192 stopped \u2192 archived`. Transition via `POST /{id}/status`.
|
|
893
|
+
"**Lifecycle.** `draft \u2192 running \u2192 stopped \u2192 archived`. Transition via `POST /{id}/status`. An archived experiment that never started can be restored with `archived \u2192 draft`; restarting an archived experiment directly is not allowed.",
|
|
866
894
|
"",
|
|
867
895
|
"**Allocation.** `allocation_pct` (basis points, 0\u201310000) is the share of the targeted audience enrolled; `groups[].weight` (must sum to 10000) splits the enrolled audience. `targeting_gate` narrows the eligible audience before allocation.",
|
|
868
896
|
"",
|
|
@@ -881,6 +909,7 @@ var experimentsResource = {
|
|
|
881
909
|
{ name: "start", description: "Transition draft experiment to running" },
|
|
882
910
|
{ name: "stop", description: "Stop a running experiment" },
|
|
883
911
|
{ name: "archive", description: "Archive a stopped experiment" },
|
|
912
|
+
{ name: "restore", description: "Restore an archived (never-started) experiment to draft" },
|
|
884
913
|
{ name: "reanalyze", description: "Re-run analysis pass for the experiment" }
|
|
885
914
|
],
|
|
886
915
|
endpoints: [
|
|
@@ -1061,8 +1090,9 @@ var experimentsResource = {
|
|
|
1061
1090
|
"- `running \u2192 stopped` \u2014 halts allocation. Existing exposures stay in the dataset.",
|
|
1062
1091
|
"- `stopped \u2192 archived` \u2014 soft-delete.",
|
|
1063
1092
|
"- `draft \u2192 archived` \u2014 discard an unstarted experiment.",
|
|
1093
|
+
"- `archived \u2192 draft` \u2014 restore a soft-deleted experiment so it can be re-completed and started. Allowed only if it never started; one that already ran must be cloned instead.",
|
|
1064
1094
|
"",
|
|
1065
|
-
"Restarting an `archived` experiment is not allowed
|
|
1095
|
+
"Restarting an `archived` experiment directly is not allowed \u2014 restore it to `draft` first. Returns `409` on illegal transitions and `429` if the plan's `experiments_running` limit is exceeded on `\u2192 running`."
|
|
1066
1096
|
].join("\n"),
|
|
1067
1097
|
pathParams: { id: "Stable opaque experiment id." },
|
|
1068
1098
|
request: experimentStatusUpdateSchema,
|
|
@@ -1083,6 +1113,11 @@ var experimentsResource = {
|
|
|
1083
1113
|
summary: "Archive \u2014 stopped \u2192 archived",
|
|
1084
1114
|
description: "Soft-deletes the experiment. List endpoints exclude archived rows by default.",
|
|
1085
1115
|
value: { status: "archived" }
|
|
1116
|
+
},
|
|
1117
|
+
restore: {
|
|
1118
|
+
summary: "Restore \u2014 archived \u2192 draft",
|
|
1119
|
+
description: "Un-deletes a soft-deleted experiment so it can be re-completed and started. Allowed only if it never ran.",
|
|
1120
|
+
value: { status: "draft" }
|
|
1086
1121
|
}
|
|
1087
1122
|
},
|
|
1088
1123
|
response: { id: "exp_01j7wb12c3d4e5f6g7h8j9k0l1", status: "running" }
|
|
@@ -59,6 +59,8 @@ interface ExperimentsClient {
|
|
|
59
59
|
start(id: string): Promise<Experiment>;
|
|
60
60
|
stop(id: string): Promise<Experiment>;
|
|
61
61
|
archive(id: string): Promise<Experiment>;
|
|
62
|
+
/** Restore a soft-deleted (archived) experiment back to `draft`. */
|
|
63
|
+
restore(id: string): Promise<Experiment>;
|
|
62
64
|
setMetrics(id: string, input: ExperimentMetricsInput): Promise<unknown>;
|
|
63
65
|
results(id: string): Promise<ExperimentResult[]>;
|
|
64
66
|
timeseries(id: string, metric?: string): Promise<ExperimentTimeseriesPoint[]>;
|
|
@@ -84,11 +86,7 @@ declare const experimentsResource: {
|
|
|
84
86
|
tag: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
85
87
|
owner_email: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
86
88
|
audience: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
87
|
-
bucket_by: z.ZodDefault<z.ZodNullable<z.
|
|
88
|
-
user_id: "user_id";
|
|
89
|
-
session_id: "session_id";
|
|
90
|
-
device_id: "device_id";
|
|
91
|
-
}>>>;
|
|
89
|
+
bucket_by: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
92
90
|
folder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
93
91
|
universe: z.ZodString;
|
|
94
92
|
targeting_gate: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
@@ -124,11 +122,7 @@ declare const experimentsResource: {
|
|
|
124
122
|
tag: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
125
123
|
owner_email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
126
124
|
audience: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
127
|
-
bucket_by: z.ZodOptional<z.ZodNullable<z.
|
|
128
|
-
user_id: "user_id";
|
|
129
|
-
session_id: "session_id";
|
|
130
|
-
device_id: "device_id";
|
|
131
|
-
}>>>;
|
|
125
|
+
bucket_by: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
132
126
|
folder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
133
127
|
targeting_gate: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
134
128
|
allocation_pct: z.ZodOptional<z.ZodNumber>;
|
|
@@ -167,6 +161,9 @@ declare const experimentsResource: {
|
|
|
167
161
|
}, {
|
|
168
162
|
readonly name: "archive";
|
|
169
163
|
readonly description: "Archive a stopped experiment";
|
|
164
|
+
}, {
|
|
165
|
+
readonly name: "restore";
|
|
166
|
+
readonly description: "Restore an archived (never-started) experiment to draft";
|
|
170
167
|
}, {
|
|
171
168
|
readonly name: "reanalyze";
|
|
172
169
|
readonly description: "Re-run analysis pass for the experiment";
|
|
@@ -186,11 +183,7 @@ declare const experimentsResource: {
|
|
|
186
183
|
tag: z.ZodNullable<z.ZodString>;
|
|
187
184
|
ownerEmail: z.ZodNullable<z.ZodString>;
|
|
188
185
|
audience: z.ZodNullable<z.ZodString>;
|
|
189
|
-
bucketBy: z.ZodNullable<z.
|
|
190
|
-
user_id: "user_id";
|
|
191
|
-
session_id: "session_id";
|
|
192
|
-
device_id: "device_id";
|
|
193
|
-
}>>;
|
|
186
|
+
bucketBy: z.ZodNullable<z.ZodString>;
|
|
194
187
|
folder: z.ZodNullable<z.ZodString>;
|
|
195
188
|
status: z.ZodEnum<{
|
|
196
189
|
draft: "draft";
|
|
@@ -273,11 +266,7 @@ declare const experimentsResource: {
|
|
|
273
266
|
tag: z.ZodNullable<z.ZodString>;
|
|
274
267
|
ownerEmail: z.ZodNullable<z.ZodString>;
|
|
275
268
|
audience: z.ZodNullable<z.ZodString>;
|
|
276
|
-
bucketBy: z.ZodNullable<z.
|
|
277
|
-
user_id: "user_id";
|
|
278
|
-
session_id: "session_id";
|
|
279
|
-
device_id: "device_id";
|
|
280
|
-
}>>;
|
|
269
|
+
bucketBy: z.ZodNullable<z.ZodString>;
|
|
281
270
|
folder: z.ZodNullable<z.ZodString>;
|
|
282
271
|
status: z.ZodEnum<{
|
|
283
272
|
draft: "draft";
|
|
@@ -352,11 +341,7 @@ declare const experimentsResource: {
|
|
|
352
341
|
tag: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
353
342
|
owner_email: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
354
343
|
audience: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
355
|
-
bucket_by: z.ZodDefault<z.ZodNullable<z.
|
|
356
|
-
user_id: "user_id";
|
|
357
|
-
session_id: "session_id";
|
|
358
|
-
device_id: "device_id";
|
|
359
|
-
}>>>;
|
|
344
|
+
bucket_by: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
360
345
|
folder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
361
346
|
universe: z.ZodString;
|
|
362
347
|
targeting_gate: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
@@ -491,11 +476,7 @@ declare const experimentsResource: {
|
|
|
491
476
|
tag: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
492
477
|
owner_email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
493
478
|
audience: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
494
|
-
bucket_by: z.ZodOptional<z.ZodNullable<z.
|
|
495
|
-
user_id: "user_id";
|
|
496
|
-
session_id: "session_id";
|
|
497
|
-
device_id: "device_id";
|
|
498
|
-
}>>>;
|
|
479
|
+
bucket_by: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
499
480
|
folder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
500
481
|
targeting_gate: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
501
482
|
allocation_pct: z.ZodOptional<z.ZodNumber>;
|
|
@@ -637,6 +618,13 @@ declare const experimentsResource: {
|
|
|
637
618
|
readonly status: "archived";
|
|
638
619
|
};
|
|
639
620
|
};
|
|
621
|
+
readonly restore: {
|
|
622
|
+
readonly summary: "Restore — archived → draft";
|
|
623
|
+
readonly description: "Un-deletes a soft-deleted experiment so it can be re-completed and started. Allowed only if it never ran.";
|
|
624
|
+
readonly value: {
|
|
625
|
+
readonly status: "draft";
|
|
626
|
+
};
|
|
627
|
+
};
|
|
640
628
|
};
|
|
641
629
|
readonly response: {
|
|
642
630
|
readonly id: "exp_01j7wb12c3d4e5f6g7h8j9k0l1";
|
|
@@ -34,7 +34,12 @@ var experimentGroupSchema = z3.object({
|
|
|
34
34
|
var paramSchemaSchema = z3.record(z3.string(), z3.enum(["string", "bool", "number"])).describe(
|
|
35
35
|
"Map of param-name \u2192 scalar type. Defines the shape of `groups[].params`. Example: `{ headline: 'string', show_cta: 'bool' }`."
|
|
36
36
|
);
|
|
37
|
-
var bucketBySchema = z3.
|
|
37
|
+
var bucketBySchema = z3.string().min(1).max(128).regex(
|
|
38
|
+
/^[a-zA-Z_][a-zA-Z0-9_]*$/,
|
|
39
|
+
"Must be a user-attribute name (letters, digits, underscore; not starting with a digit)."
|
|
40
|
+
).describe(
|
|
41
|
+
"User-attribute name used as the bucketing key \u2014 e.g. `user_id` (default), `session_id`, `device_id`, or a custom attribute like `company_id` to keep a whole org on one variant. Defaults to `user_id` when omitted."
|
|
42
|
+
);
|
|
38
43
|
var experimentCreateSchema = z3.object({
|
|
39
44
|
name: experimentNameSchema,
|
|
40
45
|
description: z3.string().max(2e3).nullable().default(null).describe("Free-form description. Max 2000 chars, markdown rendered in the dashboard."),
|
|
@@ -106,7 +111,7 @@ var experimentUpdateSchema = z3.object({
|
|
|
106
111
|
);
|
|
107
112
|
var experimentStatusUpdateSchema = z3.object({
|
|
108
113
|
status: z3.enum(["draft", "running", "stopped", "archived"]).describe(
|
|
109
|
-
"Target status. Allowed transitions: `draft \u2192 running`, `running \u2192 stopped`, `stopped \u2192 archived`, `draft \u2192 archived
|
|
114
|
+
"Target status. Allowed transitions: `draft \u2192 running`, `running \u2192 stopped`, `stopped \u2192 archived`, `draft \u2192 archived`, and `archived \u2192 draft` (restore \u2014 only if the experiment never started). Restarting an archived experiment directly is not allowed; restore it to draft first, then start."
|
|
110
115
|
)
|
|
111
116
|
}).describe("Body for `POST /api/admin/experiments/{id}/status`.");
|
|
112
117
|
var experimentMetricsUpdateSchema = z3.object({
|
|
@@ -221,24 +226,29 @@ function experimentsClient(t) {
|
|
|
221
226
|
if (opts.cursor) query.cursor = opts.cursor;
|
|
222
227
|
return t.request("GET", BASE, void 0, query);
|
|
223
228
|
}
|
|
224
|
-
async function
|
|
229
|
+
async function listAllWith(status) {
|
|
225
230
|
const out = [];
|
|
226
231
|
let cursor;
|
|
227
232
|
do {
|
|
228
|
-
const
|
|
233
|
+
const query = { limit: "500" };
|
|
234
|
+
if (cursor) query.cursor = cursor;
|
|
235
|
+
if (status) query.status = status;
|
|
236
|
+
const page = await t.request("GET", BASE, void 0, query);
|
|
229
237
|
out.push(...page.data);
|
|
230
238
|
cursor = page.next_cursor ?? void 0;
|
|
231
239
|
} while (cursor);
|
|
232
240
|
return out;
|
|
233
241
|
}
|
|
242
|
+
async function listAll() {
|
|
243
|
+
return listAllWith();
|
|
244
|
+
}
|
|
234
245
|
async function resolve(idOrName) {
|
|
235
246
|
try {
|
|
236
247
|
return await t.request("GET", `${BASE}/${idOrName}`);
|
|
237
248
|
} catch (err) {
|
|
238
249
|
if (!(err instanceof ApiError) || err.status !== 404) throw err;
|
|
239
250
|
}
|
|
240
|
-
const
|
|
241
|
-
const found = all.find((e) => e.name === idOrName);
|
|
251
|
+
const found = (await listAllWith()).find((e) => e.name === idOrName) ?? (await listAllWith("archived")).find((e) => e.name === idOrName);
|
|
242
252
|
if (!found) throw new ApiError(`Experiment '${idOrName}' not found`, 404);
|
|
243
253
|
return found;
|
|
244
254
|
}
|
|
@@ -261,6 +271,7 @@ function experimentsClient(t) {
|
|
|
261
271
|
start: (id) => setStatus(id, "running"),
|
|
262
272
|
stop: (id) => setStatus(id, "stopped"),
|
|
263
273
|
archive: (id) => setStatus(id, "archived"),
|
|
274
|
+
restore: (id) => setStatus(id, "draft"),
|
|
264
275
|
setMetrics: (id, input) => t.request("POST", `${BASE}/${id}/metrics`, experimentMetricsUpdateSchema.parse(input)),
|
|
265
276
|
results: (id) => t.request("GET", `${BASE}/${id}/results`),
|
|
266
277
|
timeseries: (id, metric) => t.request(
|
|
@@ -307,7 +318,7 @@ var experimentsResource = {
|
|
|
307
318
|
"",
|
|
308
319
|
"**Identity.** Stable `name` (a-z, 0-9, `_`/`-`, max 64 chars). Immutable after create.",
|
|
309
320
|
"",
|
|
310
|
-
"**Lifecycle.** `draft \u2192 running \u2192 stopped \u2192 archived`. Transition via `POST /{id}/status`.
|
|
321
|
+
"**Lifecycle.** `draft \u2192 running \u2192 stopped \u2192 archived`. Transition via `POST /{id}/status`. An archived experiment that never started can be restored with `archived \u2192 draft`; restarting an archived experiment directly is not allowed.",
|
|
311
322
|
"",
|
|
312
323
|
"**Allocation.** `allocation_pct` (basis points, 0\u201310000) is the share of the targeted audience enrolled; `groups[].weight` (must sum to 10000) splits the enrolled audience. `targeting_gate` narrows the eligible audience before allocation.",
|
|
313
324
|
"",
|
|
@@ -326,6 +337,7 @@ var experimentsResource = {
|
|
|
326
337
|
{ name: "start", description: "Transition draft experiment to running" },
|
|
327
338
|
{ name: "stop", description: "Stop a running experiment" },
|
|
328
339
|
{ name: "archive", description: "Archive a stopped experiment" },
|
|
340
|
+
{ name: "restore", description: "Restore an archived (never-started) experiment to draft" },
|
|
329
341
|
{ name: "reanalyze", description: "Re-run analysis pass for the experiment" }
|
|
330
342
|
],
|
|
331
343
|
endpoints: [
|
|
@@ -506,8 +518,9 @@ var experimentsResource = {
|
|
|
506
518
|
"- `running \u2192 stopped` \u2014 halts allocation. Existing exposures stay in the dataset.",
|
|
507
519
|
"- `stopped \u2192 archived` \u2014 soft-delete.",
|
|
508
520
|
"- `draft \u2192 archived` \u2014 discard an unstarted experiment.",
|
|
521
|
+
"- `archived \u2192 draft` \u2014 restore a soft-deleted experiment so it can be re-completed and started. Allowed only if it never started; one that already ran must be cloned instead.",
|
|
509
522
|
"",
|
|
510
|
-
"Restarting an `archived` experiment is not allowed
|
|
523
|
+
"Restarting an `archived` experiment directly is not allowed \u2014 restore it to `draft` first. Returns `409` on illegal transitions and `429` if the plan's `experiments_running` limit is exceeded on `\u2192 running`."
|
|
511
524
|
].join("\n"),
|
|
512
525
|
pathParams: { id: "Stable opaque experiment id." },
|
|
513
526
|
request: experimentStatusUpdateSchema,
|
|
@@ -528,6 +541,11 @@ var experimentsResource = {
|
|
|
528
541
|
summary: "Archive \u2014 stopped \u2192 archived",
|
|
529
542
|
description: "Soft-deletes the experiment. List endpoints exclude archived rows by default.",
|
|
530
543
|
value: { status: "archived" }
|
|
544
|
+
},
|
|
545
|
+
restore: {
|
|
546
|
+
summary: "Restore \u2014 archived \u2192 draft",
|
|
547
|
+
description: "Un-deletes a soft-deleted experiment so it can be re-completed and started. Allowed only if it never ran.",
|
|
548
|
+
value: { status: "draft" }
|
|
531
549
|
}
|
|
532
550
|
},
|
|
533
551
|
response: { id: "exp_01j7wb12c3d4e5f6g7h8j9k0l1", status: "running" }
|
|
@@ -90,6 +90,8 @@ declare const gatesResource: {
|
|
|
90
90
|
semver_gte: "semver_gte";
|
|
91
91
|
semver_lt: "semver_lt";
|
|
92
92
|
semver_lte: "semver_lte";
|
|
93
|
+
gate_pass: "gate_pass";
|
|
94
|
+
exp_in: "exp_in";
|
|
93
95
|
}>;
|
|
94
96
|
value: z.ZodUnknown;
|
|
95
97
|
}, z.core.$strip>>>;
|
|
@@ -120,9 +122,20 @@ declare const gatesResource: {
|
|
|
120
122
|
semver_gte: "semver_gte";
|
|
121
123
|
semver_lt: "semver_lt";
|
|
122
124
|
semver_lte: "semver_lte";
|
|
125
|
+
gate_pass: "gate_pass";
|
|
126
|
+
exp_in: "exp_in";
|
|
123
127
|
}>;
|
|
124
128
|
value: z.ZodUnknown;
|
|
125
129
|
}, z.core.$strip>>>;
|
|
130
|
+
rolloutPct: z.ZodOptional<z.ZodNumber>;
|
|
131
|
+
bucketBy: z.ZodOptional<z.ZodString>;
|
|
132
|
+
salt: z.ZodOptional<z.ZodString>;
|
|
133
|
+
ramp: z.ZodOptional<z.ZodObject<{
|
|
134
|
+
from: z.ZodNumber;
|
|
135
|
+
to: z.ZodNumber;
|
|
136
|
+
startAt: z.ZodNumber;
|
|
137
|
+
durationMs: z.ZodNumber;
|
|
138
|
+
}, z.core.$strip>>;
|
|
126
139
|
locked: z.ZodOptional<z.ZodBoolean>;
|
|
127
140
|
}, z.core.$strip>, z.ZodObject<{
|
|
128
141
|
id: z.ZodString;
|
|
@@ -132,6 +145,12 @@ declare const gatesResource: {
|
|
|
132
145
|
rolloutPct: z.ZodNumber;
|
|
133
146
|
bucketBy: z.ZodOptional<z.ZodString>;
|
|
134
147
|
salt: z.ZodOptional<z.ZodString>;
|
|
148
|
+
ramp: z.ZodOptional<z.ZodObject<{
|
|
149
|
+
from: z.ZodNumber;
|
|
150
|
+
to: z.ZodNumber;
|
|
151
|
+
startAt: z.ZodNumber;
|
|
152
|
+
durationMs: z.ZodNumber;
|
|
153
|
+
}, z.core.$strip>>;
|
|
135
154
|
locked: z.ZodOptional<z.ZodBoolean>;
|
|
136
155
|
}, z.core.$strip>], "type">>>>;
|
|
137
156
|
}, z.core.$strip>;
|
|
@@ -160,6 +179,8 @@ declare const gatesResource: {
|
|
|
160
179
|
semver_gte: "semver_gte";
|
|
161
180
|
semver_lt: "semver_lt";
|
|
162
181
|
semver_lte: "semver_lte";
|
|
182
|
+
gate_pass: "gate_pass";
|
|
183
|
+
exp_in: "exp_in";
|
|
163
184
|
}>;
|
|
164
185
|
value: z.ZodUnknown;
|
|
165
186
|
}, z.core.$strip>>>;
|
|
@@ -190,9 +211,20 @@ declare const gatesResource: {
|
|
|
190
211
|
semver_gte: "semver_gte";
|
|
191
212
|
semver_lt: "semver_lt";
|
|
192
213
|
semver_lte: "semver_lte";
|
|
214
|
+
gate_pass: "gate_pass";
|
|
215
|
+
exp_in: "exp_in";
|
|
193
216
|
}>;
|
|
194
217
|
value: z.ZodUnknown;
|
|
195
218
|
}, z.core.$strip>>>;
|
|
219
|
+
rolloutPct: z.ZodOptional<z.ZodNumber>;
|
|
220
|
+
bucketBy: z.ZodOptional<z.ZodString>;
|
|
221
|
+
salt: z.ZodOptional<z.ZodString>;
|
|
222
|
+
ramp: z.ZodOptional<z.ZodObject<{
|
|
223
|
+
from: z.ZodNumber;
|
|
224
|
+
to: z.ZodNumber;
|
|
225
|
+
startAt: z.ZodNumber;
|
|
226
|
+
durationMs: z.ZodNumber;
|
|
227
|
+
}, z.core.$strip>>;
|
|
196
228
|
locked: z.ZodOptional<z.ZodBoolean>;
|
|
197
229
|
}, z.core.$strip>, z.ZodObject<{
|
|
198
230
|
id: z.ZodString;
|
|
@@ -202,6 +234,12 @@ declare const gatesResource: {
|
|
|
202
234
|
rolloutPct: z.ZodNumber;
|
|
203
235
|
bucketBy: z.ZodOptional<z.ZodString>;
|
|
204
236
|
salt: z.ZodOptional<z.ZodString>;
|
|
237
|
+
ramp: z.ZodOptional<z.ZodObject<{
|
|
238
|
+
from: z.ZodNumber;
|
|
239
|
+
to: z.ZodNumber;
|
|
240
|
+
startAt: z.ZodNumber;
|
|
241
|
+
durationMs: z.ZodNumber;
|
|
242
|
+
}, z.core.$strip>>;
|
|
205
243
|
locked: z.ZodOptional<z.ZodBoolean>;
|
|
206
244
|
}, z.core.$strip>], "type">>>>;
|
|
207
245
|
}, z.core.$strip>;
|
|
@@ -248,6 +286,8 @@ declare const gatesResource: {
|
|
|
248
286
|
semver_gte: "semver_gte";
|
|
249
287
|
semver_lt: "semver_lt";
|
|
250
288
|
semver_lte: "semver_lte";
|
|
289
|
+
gate_pass: "gate_pass";
|
|
290
|
+
exp_in: "exp_in";
|
|
251
291
|
}>;
|
|
252
292
|
value: z.ZodUnknown;
|
|
253
293
|
}, z.core.$strip>>>;
|
|
@@ -283,9 +323,20 @@ declare const gatesResource: {
|
|
|
283
323
|
semver_gte: "semver_gte";
|
|
284
324
|
semver_lt: "semver_lt";
|
|
285
325
|
semver_lte: "semver_lte";
|
|
326
|
+
gate_pass: "gate_pass";
|
|
327
|
+
exp_in: "exp_in";
|
|
286
328
|
}>;
|
|
287
329
|
value: z.ZodUnknown;
|
|
288
330
|
}, z.core.$strip>>>;
|
|
331
|
+
rolloutPct: z.ZodOptional<z.ZodNumber>;
|
|
332
|
+
bucketBy: z.ZodOptional<z.ZodString>;
|
|
333
|
+
salt: z.ZodOptional<z.ZodString>;
|
|
334
|
+
ramp: z.ZodOptional<z.ZodObject<{
|
|
335
|
+
from: z.ZodNumber;
|
|
336
|
+
to: z.ZodNumber;
|
|
337
|
+
startAt: z.ZodNumber;
|
|
338
|
+
durationMs: z.ZodNumber;
|
|
339
|
+
}, z.core.$strip>>;
|
|
289
340
|
locked: z.ZodOptional<z.ZodBoolean>;
|
|
290
341
|
}, z.core.$strip>, z.ZodObject<{
|
|
291
342
|
id: z.ZodString;
|
|
@@ -295,6 +346,12 @@ declare const gatesResource: {
|
|
|
295
346
|
rolloutPct: z.ZodNumber;
|
|
296
347
|
bucketBy: z.ZodOptional<z.ZodString>;
|
|
297
348
|
salt: z.ZodOptional<z.ZodString>;
|
|
349
|
+
ramp: z.ZodOptional<z.ZodObject<{
|
|
350
|
+
from: z.ZodNumber;
|
|
351
|
+
to: z.ZodNumber;
|
|
352
|
+
startAt: z.ZodNumber;
|
|
353
|
+
durationMs: z.ZodNumber;
|
|
354
|
+
}, z.core.$strip>>;
|
|
298
355
|
locked: z.ZodOptional<z.ZodBoolean>;
|
|
299
356
|
}, z.core.$strip>], "type">>>>;
|
|
300
357
|
updatedAt: z.ZodString;
|
|
@@ -364,6 +421,8 @@ declare const gatesResource: {
|
|
|
364
421
|
semver_gte: "semver_gte";
|
|
365
422
|
semver_lt: "semver_lt";
|
|
366
423
|
semver_lte: "semver_lte";
|
|
424
|
+
gate_pass: "gate_pass";
|
|
425
|
+
exp_in: "exp_in";
|
|
367
426
|
}>;
|
|
368
427
|
value: z.ZodUnknown;
|
|
369
428
|
}, z.core.$strip>>>;
|
|
@@ -394,9 +453,20 @@ declare const gatesResource: {
|
|
|
394
453
|
semver_gte: "semver_gte";
|
|
395
454
|
semver_lt: "semver_lt";
|
|
396
455
|
semver_lte: "semver_lte";
|
|
456
|
+
gate_pass: "gate_pass";
|
|
457
|
+
exp_in: "exp_in";
|
|
397
458
|
}>;
|
|
398
459
|
value: z.ZodUnknown;
|
|
399
460
|
}, z.core.$strip>>>;
|
|
461
|
+
rolloutPct: z.ZodOptional<z.ZodNumber>;
|
|
462
|
+
bucketBy: z.ZodOptional<z.ZodString>;
|
|
463
|
+
salt: z.ZodOptional<z.ZodString>;
|
|
464
|
+
ramp: z.ZodOptional<z.ZodObject<{
|
|
465
|
+
from: z.ZodNumber;
|
|
466
|
+
to: z.ZodNumber;
|
|
467
|
+
startAt: z.ZodNumber;
|
|
468
|
+
durationMs: z.ZodNumber;
|
|
469
|
+
}, z.core.$strip>>;
|
|
400
470
|
locked: z.ZodOptional<z.ZodBoolean>;
|
|
401
471
|
}, z.core.$strip>, z.ZodObject<{
|
|
402
472
|
id: z.ZodString;
|
|
@@ -406,6 +476,12 @@ declare const gatesResource: {
|
|
|
406
476
|
rolloutPct: z.ZodNumber;
|
|
407
477
|
bucketBy: z.ZodOptional<z.ZodString>;
|
|
408
478
|
salt: z.ZodOptional<z.ZodString>;
|
|
479
|
+
ramp: z.ZodOptional<z.ZodObject<{
|
|
480
|
+
from: z.ZodNumber;
|
|
481
|
+
to: z.ZodNumber;
|
|
482
|
+
startAt: z.ZodNumber;
|
|
483
|
+
durationMs: z.ZodNumber;
|
|
484
|
+
}, z.core.$strip>>;
|
|
409
485
|
locked: z.ZodOptional<z.ZodBoolean>;
|
|
410
486
|
}, z.core.$strip>], "type">>>>;
|
|
411
487
|
}, z.core.$strip>;
|
|
@@ -531,6 +607,8 @@ declare const gatesResource: {
|
|
|
531
607
|
semver_gte: "semver_gte";
|
|
532
608
|
semver_lt: "semver_lt";
|
|
533
609
|
semver_lte: "semver_lte";
|
|
610
|
+
gate_pass: "gate_pass";
|
|
611
|
+
exp_in: "exp_in";
|
|
534
612
|
}>;
|
|
535
613
|
value: z.ZodUnknown;
|
|
536
614
|
}, z.core.$strip>>>;
|
|
@@ -561,9 +639,20 @@ declare const gatesResource: {
|
|
|
561
639
|
semver_gte: "semver_gte";
|
|
562
640
|
semver_lt: "semver_lt";
|
|
563
641
|
semver_lte: "semver_lte";
|
|
642
|
+
gate_pass: "gate_pass";
|
|
643
|
+
exp_in: "exp_in";
|
|
564
644
|
}>;
|
|
565
645
|
value: z.ZodUnknown;
|
|
566
646
|
}, z.core.$strip>>>;
|
|
647
|
+
rolloutPct: z.ZodOptional<z.ZodNumber>;
|
|
648
|
+
bucketBy: z.ZodOptional<z.ZodString>;
|
|
649
|
+
salt: z.ZodOptional<z.ZodString>;
|
|
650
|
+
ramp: z.ZodOptional<z.ZodObject<{
|
|
651
|
+
from: z.ZodNumber;
|
|
652
|
+
to: z.ZodNumber;
|
|
653
|
+
startAt: z.ZodNumber;
|
|
654
|
+
durationMs: z.ZodNumber;
|
|
655
|
+
}, z.core.$strip>>;
|
|
567
656
|
locked: z.ZodOptional<z.ZodBoolean>;
|
|
568
657
|
}, z.core.$strip>, z.ZodObject<{
|
|
569
658
|
id: z.ZodString;
|
|
@@ -573,6 +662,12 @@ declare const gatesResource: {
|
|
|
573
662
|
rolloutPct: z.ZodNumber;
|
|
574
663
|
bucketBy: z.ZodOptional<z.ZodString>;
|
|
575
664
|
salt: z.ZodOptional<z.ZodString>;
|
|
665
|
+
ramp: z.ZodOptional<z.ZodObject<{
|
|
666
|
+
from: z.ZodNumber;
|
|
667
|
+
to: z.ZodNumber;
|
|
668
|
+
startAt: z.ZodNumber;
|
|
669
|
+
durationMs: z.ZodNumber;
|
|
670
|
+
}, z.core.$strip>>;
|
|
576
671
|
locked: z.ZodOptional<z.ZodBoolean>;
|
|
577
672
|
}, z.core.$strip>], "type">>>>;
|
|
578
673
|
}, z.core.$strip>;
|