@shipeasy/openapi 1.0.0 → 1.2.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.
Files changed (40) hide show
  1. package/dist/index.d.ts +9 -7
  2. package/dist/index.js +504 -2672
  3. package/dist/resources/alert-rules.d.ts +48 -0
  4. package/dist/resources/alert-rules.js +66 -0
  5. package/dist/resources/configs.d.ts +6 -2
  6. package/dist/resources/configs.js +67 -2375
  7. package/dist/resources/experiments.d.ts +100 -2
  8. package/dist/resources/experiments.js +158 -105
  9. package/dist/resources/gates.d.ts +51 -8
  10. package/dist/resources/gates.js +102 -70
  11. package/dist/resources/index.d.ts +166 -10
  12. package/dist/resources/index.js +503 -2671
  13. package/dist/resources/killswitches.d.ts +6 -2
  14. package/dist/resources/killswitches.js +113 -2412
  15. package/dist/resources/universes.d.ts +7 -4
  16. package/dist/resources/universes.js +30 -20
  17. package/dist/schemas/alert-rules.d.ts +43 -0
  18. package/dist/schemas/alert-rules.js +27 -0
  19. package/dist/schemas/configs.d.ts +154 -0
  20. package/dist/schemas/configs.js +134 -0
  21. package/dist/schemas/constants.d.ts +6 -0
  22. package/dist/schemas/constants.js +7 -0
  23. package/dist/schemas/experiments.d.ts +245 -0
  24. package/dist/schemas/experiments.js +216 -0
  25. package/dist/schemas/folder.d.ts +10 -0
  26. package/dist/schemas/folder.js +8 -0
  27. package/dist/schemas/gates.d.ts +304 -0
  28. package/dist/schemas/gates.js +172 -0
  29. package/dist/schemas/index.d.ts +11 -0
  30. package/dist/schemas/index.js +729 -0
  31. package/dist/schemas/killswitches.d.ts +110 -0
  32. package/dist/schemas/killswitches.js +226 -0
  33. package/dist/schemas/metric-name.d.ts +5 -0
  34. package/dist/schemas/metric-name.js +6 -0
  35. package/dist/schemas/pagination.d.ts +37 -0
  36. package/dist/schemas/pagination.js +44 -0
  37. package/dist/schemas/universes.d.ts +35 -0
  38. package/dist/schemas/universes.js +55 -0
  39. package/openapi.json +1257 -228
  40. package/package.json +13 -10
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
- import { experimentCreateSchema, experimentUpdateSchema } from '@shipeasy/core/schemas/experiments';
3
- import { PageQuery, Page } from '@shipeasy/core/pagination';
2
+ import { experimentCreateSchema, experimentUpdateSchema } from '../schemas/experiments.js';
3
+ import { PageQuery, Page } from '../schemas/pagination.js';
4
4
  import { Transport } from '../transport.js';
5
5
 
6
6
  /**
@@ -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[]>;
@@ -80,7 +82,16 @@ declare const experimentsResource: {
80
82
  readonly create: z.ZodObject<{
81
83
  name: z.ZodString;
82
84
  description: z.ZodDefault<z.ZodNullable<z.ZodString>>;
85
+ hypothesis: z.ZodDefault<z.ZodNullable<z.ZodString>>;
83
86
  tag: z.ZodDefault<z.ZodNullable<z.ZodString>>;
87
+ owner_email: z.ZodDefault<z.ZodNullable<z.ZodString>>;
88
+ audience: z.ZodDefault<z.ZodNullable<z.ZodString>>;
89
+ bucket_by: z.ZodDefault<z.ZodNullable<z.ZodEnum<{
90
+ user_id: "user_id";
91
+ session_id: "session_id";
92
+ device_id: "device_id";
93
+ }>>>;
94
+ folder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
84
95
  universe: z.ZodString;
85
96
  targeting_gate: z.ZodDefault<z.ZodNullable<z.ZodString>>;
86
97
  allocation_pct: z.ZodDefault<z.ZodNumber>;
@@ -99,11 +110,28 @@ declare const experimentsResource: {
99
110
  min_runtime_days: z.ZodDefault<z.ZodNumber>;
100
111
  min_sample_size: z.ZodDefault<z.ZodNumber>;
101
112
  sequential_testing: z.ZodDefault<z.ZodBoolean>;
113
+ goal_metric: z.ZodOptional<z.ZodObject<{
114
+ name: z.ZodOptional<z.ZodString>;
115
+ query: z.ZodString;
116
+ }, z.core.$strip>>;
117
+ guardrail_metrics: z.ZodDefault<z.ZodArray<z.ZodObject<{
118
+ name: z.ZodOptional<z.ZodString>;
119
+ query: z.ZodString;
120
+ }, z.core.$strip>>>;
102
121
  }, z.core.$strip>;
103
122
  readonly update: z.ZodObject<{
104
123
  name: z.ZodOptional<z.ZodString>;
105
124
  description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
125
+ hypothesis: z.ZodOptional<z.ZodNullable<z.ZodString>>;
106
126
  tag: z.ZodOptional<z.ZodNullable<z.ZodString>>;
127
+ owner_email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
128
+ audience: z.ZodOptional<z.ZodNullable<z.ZodString>>;
129
+ bucket_by: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
130
+ user_id: "user_id";
131
+ session_id: "session_id";
132
+ device_id: "device_id";
133
+ }>>>;
134
+ folder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
107
135
  targeting_gate: z.ZodOptional<z.ZodNullable<z.ZodString>>;
108
136
  allocation_pct: z.ZodOptional<z.ZodNumber>;
109
137
  salt: z.ZodOptional<z.ZodString>;
@@ -122,6 +150,14 @@ declare const experimentsResource: {
122
150
  min_runtime_days: z.ZodOptional<z.ZodNumber>;
123
151
  min_sample_size: z.ZodOptional<z.ZodNumber>;
124
152
  sequential_testing: z.ZodOptional<z.ZodBoolean>;
153
+ goal_metric: z.ZodOptional<z.ZodObject<{
154
+ name: z.ZodOptional<z.ZodString>;
155
+ query: z.ZodString;
156
+ }, z.core.$strip>>;
157
+ guardrail_metrics: z.ZodOptional<z.ZodArray<z.ZodObject<{
158
+ name: z.ZodOptional<z.ZodString>;
159
+ query: z.ZodString;
160
+ }, z.core.$strip>>>;
125
161
  }, z.core.$strip>;
126
162
  };
127
163
  readonly actions: readonly [{
@@ -133,6 +169,9 @@ declare const experimentsResource: {
133
169
  }, {
134
170
  readonly name: "archive";
135
171
  readonly description: "Archive a stopped experiment";
172
+ }, {
173
+ readonly name: "restore";
174
+ readonly description: "Restore an archived (never-started) experiment to draft";
136
175
  }, {
137
176
  readonly name: "reanalyze";
138
177
  readonly description: "Re-run analysis pass for the experiment";
@@ -148,7 +187,16 @@ declare const experimentsResource: {
148
187
  id: z.ZodString;
149
188
  name: z.ZodString;
150
189
  description: z.ZodNullable<z.ZodString>;
190
+ hypothesis: z.ZodNullable<z.ZodString>;
151
191
  tag: z.ZodNullable<z.ZodString>;
192
+ ownerEmail: z.ZodNullable<z.ZodString>;
193
+ audience: z.ZodNullable<z.ZodString>;
194
+ bucketBy: z.ZodNullable<z.ZodEnum<{
195
+ user_id: "user_id";
196
+ session_id: "session_id";
197
+ device_id: "device_id";
198
+ }>>;
199
+ folder: z.ZodNullable<z.ZodString>;
152
200
  status: z.ZodEnum<{
153
201
  draft: "draft";
154
202
  running: "running";
@@ -226,7 +274,16 @@ declare const experimentsResource: {
226
274
  id: z.ZodString;
227
275
  name: z.ZodString;
228
276
  description: z.ZodNullable<z.ZodString>;
277
+ hypothesis: z.ZodNullable<z.ZodString>;
229
278
  tag: z.ZodNullable<z.ZodString>;
279
+ ownerEmail: z.ZodNullable<z.ZodString>;
280
+ audience: z.ZodNullable<z.ZodString>;
281
+ bucketBy: z.ZodNullable<z.ZodEnum<{
282
+ user_id: "user_id";
283
+ session_id: "session_id";
284
+ device_id: "device_id";
285
+ }>>;
286
+ folder: z.ZodNullable<z.ZodString>;
230
287
  status: z.ZodEnum<{
231
288
  draft: "draft";
232
289
  running: "running";
@@ -296,7 +353,16 @@ declare const experimentsResource: {
296
353
  readonly request: z.ZodObject<{
297
354
  name: z.ZodString;
298
355
  description: z.ZodDefault<z.ZodNullable<z.ZodString>>;
356
+ hypothesis: z.ZodDefault<z.ZodNullable<z.ZodString>>;
299
357
  tag: z.ZodDefault<z.ZodNullable<z.ZodString>>;
358
+ owner_email: z.ZodDefault<z.ZodNullable<z.ZodString>>;
359
+ audience: z.ZodDefault<z.ZodNullable<z.ZodString>>;
360
+ bucket_by: z.ZodDefault<z.ZodNullable<z.ZodEnum<{
361
+ user_id: "user_id";
362
+ session_id: "session_id";
363
+ device_id: "device_id";
364
+ }>>>;
365
+ folder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
300
366
  universe: z.ZodString;
301
367
  targeting_gate: z.ZodDefault<z.ZodNullable<z.ZodString>>;
302
368
  allocation_pct: z.ZodDefault<z.ZodNumber>;
@@ -315,6 +381,14 @@ declare const experimentsResource: {
315
381
  min_runtime_days: z.ZodDefault<z.ZodNumber>;
316
382
  min_sample_size: z.ZodDefault<z.ZodNumber>;
317
383
  sequential_testing: z.ZodDefault<z.ZodBoolean>;
384
+ goal_metric: z.ZodOptional<z.ZodObject<{
385
+ name: z.ZodOptional<z.ZodString>;
386
+ query: z.ZodString;
387
+ }, z.core.$strip>>;
388
+ guardrail_metrics: z.ZodDefault<z.ZodArray<z.ZodObject<{
389
+ name: z.ZodOptional<z.ZodString>;
390
+ query: z.ZodString;
391
+ }, z.core.$strip>>>;
318
392
  }, z.core.$strip>;
319
393
  readonly response: z.ZodObject<{
320
394
  id: z.ZodString;
@@ -418,7 +492,16 @@ declare const experimentsResource: {
418
492
  readonly request: z.ZodObject<{
419
493
  name: z.ZodOptional<z.ZodString>;
420
494
  description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
495
+ hypothesis: z.ZodOptional<z.ZodNullable<z.ZodString>>;
421
496
  tag: z.ZodOptional<z.ZodNullable<z.ZodString>>;
497
+ owner_email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
498
+ audience: z.ZodOptional<z.ZodNullable<z.ZodString>>;
499
+ bucket_by: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
500
+ user_id: "user_id";
501
+ session_id: "session_id";
502
+ device_id: "device_id";
503
+ }>>>;
504
+ folder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
422
505
  targeting_gate: z.ZodOptional<z.ZodNullable<z.ZodString>>;
423
506
  allocation_pct: z.ZodOptional<z.ZodNumber>;
424
507
  salt: z.ZodOptional<z.ZodString>;
@@ -437,6 +520,14 @@ declare const experimentsResource: {
437
520
  min_runtime_days: z.ZodOptional<z.ZodNumber>;
438
521
  min_sample_size: z.ZodOptional<z.ZodNumber>;
439
522
  sequential_testing: z.ZodOptional<z.ZodBoolean>;
523
+ goal_metric: z.ZodOptional<z.ZodObject<{
524
+ name: z.ZodOptional<z.ZodString>;
525
+ query: z.ZodString;
526
+ }, z.core.$strip>>;
527
+ guardrail_metrics: z.ZodOptional<z.ZodArray<z.ZodObject<{
528
+ name: z.ZodOptional<z.ZodString>;
529
+ query: z.ZodString;
530
+ }, z.core.$strip>>>;
440
531
  }, z.core.$strip>;
441
532
  readonly response: z.ZodObject<{
442
533
  id: z.ZodString;
@@ -551,6 +642,13 @@ declare const experimentsResource: {
551
642
  readonly status: "archived";
552
643
  };
553
644
  };
645
+ readonly restore: {
646
+ readonly summary: "Restore — archived → draft";
647
+ readonly description: "Un-deletes a soft-deleted experiment so it can be re-completed and started. Allowed only if it never ran.";
648
+ readonly value: {
649
+ readonly status: "draft";
650
+ };
651
+ };
554
652
  };
555
653
  readonly response: {
556
654
  readonly id: "exp_01j7wb12c3d4e5f6g7h8j9k0l1";
@@ -1,48 +1,76 @@
1
1
  // src/resources/experiments.ts
2
- import { z as z2 } from "zod";
2
+ import { z as z4 } from "zod";
3
+
4
+ // src/schemas/experiments.ts
5
+ import { z as z3 } from "zod";
3
6
 
4
- // ../core/src/schemas/experiments.ts
7
+ // src/schemas/folder.ts
5
8
  import { z } from "zod";
6
- var experimentNameSchema = z.string().regex(/^[a-z0-9][a-z0-9_-]{0,63}$/).describe(
7
- "Stable experiment key (a-z, 0-9, `_`/`-`, must start with letter/digit, max 64 chars). Used by SDKs as `Shipeasy.getExperiment(user, '<name>')`. Immutable after create."
9
+ var folderSchema = z.string().max(256).regex(/^[a-zA-Z0-9_-]+$/, "Folder must be alphanumeric, `_` or `-` (no `/`).").nullable().optional().describe(
10
+ "Optional folder name used to group items in the dashboard. Part of the SDK lookup key: an item in folder `checkout` named `new-cart` is referenced as `checkout/new-cart` from the SDK."
11
+ );
12
+
13
+ // src/schemas/metric-name.ts
14
+ import { z as z2 } from "zod";
15
+ var metricNameSchema = z2.string().regex(/^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)?$/).max(128);
16
+
17
+ // src/schemas/experiments.ts
18
+ var experimentInlineMetricSchema = z3.object({
19
+ name: metricNameSchema.optional(),
20
+ query: z3.string().min(1).max(4096).describe("Metric DSL string, e.g. `count_users(checkout_completed)`.")
21
+ });
22
+ var experimentNameSchema = z3.string().regex(/^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)?$/).max(128).describe(
23
+ "Stable experiment key. Single segment or `folder.name` (a-z, 0-9, `_`/`-`; max 128 chars). Used by SDKs as `Shipeasy.getExperiment(user, '<name>')`. Immutable after create."
8
24
  );
9
- var experimentGroupSchema = z.object({
10
- name: z.string().min(1).max(64).describe("Group label (e.g. `control`, `treatment_a`). Max 64 chars."),
11
- weight: z.number().int().min(0).max(1e4).describe(
25
+ var experimentGroupSchema = z3.object({
26
+ name: z3.string().min(1).max(64).describe("Group label (e.g. `control`, `treatment_a`). Max 64 chars."),
27
+ weight: z3.number().int().min(0).max(1e4).describe(
12
28
  "Allocation weight in basis points (0\u201310000). The sum across all groups must equal exactly **10000** (100%)."
13
29
  ),
14
- params: z.record(z.string(), z.unknown()).default({}).describe(
30
+ params: z3.record(z3.string(), z3.unknown()).default({}).describe(
15
31
  "Per-group parameter values delivered to the SDK when a caller is hashed into this group. Keys must match the experiment's `params` schema."
16
32
  )
17
33
  }).describe("One variant in the experiment. Weights across all groups must sum to 10000.");
18
- var paramSchemaSchema = z.record(z.string(), z.enum(["string", "bool", "number"])).describe(
34
+ var paramSchemaSchema = z3.record(z3.string(), z3.enum(["string", "bool", "number"])).describe(
19
35
  "Map of param-name \u2192 scalar type. Defines the shape of `groups[].params`. Example: `{ headline: 'string', show_cta: 'bool' }`."
20
36
  );
21
- var experimentCreateSchema = z.object({
37
+ var bucketBySchema = z3.enum(["user_id", "session_id", "device_id"]).describe("Identifier used as the bucketing key. `user_id` is sticky across sessions.");
38
+ var experimentCreateSchema = z3.object({
22
39
  name: experimentNameSchema,
23
- description: z.string().max(2e3).nullable().default(null).describe("Free-form description. Max 2000 chars, markdown rendered in the dashboard."),
24
- tag: z.string().max(64).nullable().default(null).describe("Optional tag used to group experiments in the dashboard."),
25
- universe: z.string().min(1).describe(
40
+ description: z3.string().max(2e3).nullable().default(null).describe("Free-form description. Max 2000 chars, markdown rendered in the dashboard."),
41
+ hypothesis: z3.string().max(4e3).nullable().default(null).describe("Hypothesis statement shown in the editor. Display-only."),
42
+ tag: z3.string().max(64).nullable().default(null).describe("Short tag chip rendered next to the name. Display-only."),
43
+ owner_email: z3.string().max(254).nullable().default(null).describe("Owner email. Display-only."),
44
+ audience: z3.string().max(256).nullable().default(null).describe("Audience label shown in the editor. Display-only."),
45
+ bucket_by: bucketBySchema.nullable().default(null),
46
+ folder: folderSchema,
47
+ universe: z3.string().min(1).describe(
26
48
  "Name of an existing universe in the project. Returns `422` if the universe doesn't exist."
27
49
  ),
28
- targeting_gate: z.string().nullable().default(null).describe(
50
+ targeting_gate: z3.string().nullable().default(null).describe(
29
51
  "Optional gate name. Only callers that pass the gate are enrolled in the experiment."
30
52
  ),
31
- allocation_pct: z.number().int().min(0).max(1e4).default(0).describe(
53
+ allocation_pct: z3.number().int().min(0).max(1e4).default(0).describe(
32
54
  "Share of the (gated) audience allocated to the experiment, in basis points (0\u201310000 = 0%\u2013100%). `0` = unallocated. Immutable while the experiment is running."
33
55
  ),
34
- salt: z.string().min(1).max(64).optional().describe("Hash salt for bucketing. Auto-generated if omitted. Immutable while running."),
56
+ salt: z3.string().min(1).max(64).optional().describe("Hash salt for bucketing. Auto-generated if omitted. Immutable while running."),
35
57
  params: paramSchemaSchema.default({}),
36
- groups: z.array(experimentGroupSchema).min(2).describe(
58
+ groups: z3.array(experimentGroupSchema).min(2).describe(
37
59
  "Two or more variants. Weights must sum to exactly 10000 (100%). Immutable while running."
38
60
  ),
39
- significance_threshold: z.number().min(1e-4).max(0.5).default(0.05).describe(
61
+ significance_threshold: z3.number().min(1e-4).max(0.5).default(0.05).describe(
40
62
  "p-value cutoff used by the analysis pass. Defaults to `0.05`. Values other than 0.05 require Pro plan or higher."
41
63
  ),
42
- min_runtime_days: z.number().int().min(0).max(365).default(0).describe("Minimum days the experiment must run before results are considered conclusive."),
43
- min_sample_size: z.number().int().min(1).default(100).describe("Minimum exposures per group before results are considered conclusive."),
44
- sequential_testing: z.boolean().default(false).describe(
64
+ min_runtime_days: z3.number().int().min(0).max(365).default(0).describe("Minimum days the experiment must run before results are considered conclusive."),
65
+ min_sample_size: z3.number().int().min(1).default(100).describe("Minimum exposures per group before results are considered conclusive."),
66
+ sequential_testing: z3.boolean().default(false).describe(
45
67
  "Enable sequential testing (always-valid p-values). Requires Premium plan or higher."
68
+ ),
69
+ goal_metric: experimentInlineMetricSchema.optional().describe(
70
+ "Single goal metric defined inline as a DSL query. The underlying event is auto-created if missing."
71
+ ),
72
+ guardrail_metrics: z3.array(experimentInlineMetricSchema).max(10).default([]).describe(
73
+ "Up to 10 guardrail metrics defined inline. Each is upserted (event + metric) and attached with role=guardrail."
46
74
  )
47
75
  }).refine(
48
76
  (e) => e.groups.reduce((s, g) => s + g.weight, 0) === 1e4,
@@ -50,35 +78,42 @@ var experimentCreateSchema = z.object({
50
78
  ).describe(
51
79
  "Body for `POST /api/admin/experiments`. `name`, `universe`, and `groups` (\u22652, weights sum to 10000) required."
52
80
  );
53
- var experimentUpdateSchema = z.object({
81
+ var experimentUpdateSchema = z3.object({
54
82
  name: experimentNameSchema.optional(),
55
- description: z.string().max(2e3).nullable().optional(),
56
- tag: z.string().max(64).nullable().optional(),
57
- targeting_gate: z.string().nullable().optional(),
58
- allocation_pct: z.number().int().min(0).max(1e4).optional().describe("Basis-points allocation (0\u201310000). Immutable while the experiment is running."),
59
- salt: z.string().min(1).max(64).optional().describe("Hash salt. Immutable while running."),
60
- universe: z.string().optional().describe(
83
+ description: z3.string().max(2e3).nullable().optional(),
84
+ hypothesis: z3.string().max(4e3).nullable().optional(),
85
+ tag: z3.string().max(64).nullable().optional(),
86
+ owner_email: z3.string().max(254).nullable().optional(),
87
+ audience: z3.string().max(256).nullable().optional(),
88
+ bucket_by: bucketBySchema.nullable().optional(),
89
+ folder: folderSchema,
90
+ targeting_gate: z3.string().nullable().optional(),
91
+ allocation_pct: z3.number().int().min(0).max(1e4).optional().describe("Basis-points allocation (0\u201310000). Immutable while the experiment is running."),
92
+ salt: z3.string().min(1).max(64).optional().describe("Hash salt. Immutable while running."),
93
+ universe: z3.string().optional().describe(
61
94
  "New universe name. Immutable while running. Returns `422` if the universe doesn't exist."
62
95
  ),
63
96
  params: paramSchemaSchema.optional(),
64
- groups: z.array(experimentGroupSchema).min(2).optional().describe("Replacement groups. Weights must sum to 10000. Immutable while running."),
65
- significance_threshold: z.number().min(1e-4).max(0.5).optional(),
66
- min_runtime_days: z.number().int().min(0).max(365).optional(),
67
- min_sample_size: z.number().int().min(1).optional(),
68
- sequential_testing: z.boolean().optional()
97
+ groups: z3.array(experimentGroupSchema).min(2).optional().describe("Replacement groups. Weights must sum to 10000. Immutable while running."),
98
+ significance_threshold: z3.number().min(1e-4).max(0.5).optional(),
99
+ min_runtime_days: z3.number().int().min(0).max(365).optional(),
100
+ min_sample_size: z3.number().int().min(1).optional(),
101
+ sequential_testing: z3.boolean().optional(),
102
+ goal_metric: experimentInlineMetricSchema.optional().describe("Replaces the goal metric (event auto-upserted)."),
103
+ guardrail_metrics: z3.array(experimentInlineMetricSchema).max(10).optional().describe("Replaces the guardrail set wholesale (event auto-upserted per entry).")
69
104
  }).describe(
70
105
  "Body for `PATCH /api/admin/experiments/{id}`. Partial \u2014 only supplied fields change. `allocation_pct`, `groups`, `salt`, `universe`, `params` are immutable while the experiment is running (stop first)."
71
106
  );
72
- var experimentStatusUpdateSchema = z.object({
73
- status: z.enum(["draft", "running", "stopped", "archived"]).describe(
74
- "Target status. Allowed transitions: `draft \u2192 running`, `running \u2192 stopped`, `stopped \u2192 archived`, `draft \u2192 archived`. Restarting an archived experiment is not allowed."
107
+ var experimentStatusUpdateSchema = z3.object({
108
+ 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`, 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."
75
110
  )
76
111
  }).describe("Body for `POST /api/admin/experiments/{id}/status`.");
77
- var experimentMetricsUpdateSchema = z.object({
78
- metrics: z.array(
79
- z.object({
80
- metric_id: z.string().describe("Existing metric id in the project."),
81
- role: z.enum(["goal", "guardrail", "secondary"]).describe(
112
+ var experimentMetricsUpdateSchema = z3.object({
113
+ metrics: z3.array(
114
+ z3.object({
115
+ metric_id: z3.string().describe("Existing metric id in the project."),
116
+ role: z3.enum(["goal", "guardrail", "secondary"]).describe(
82
117
  "Metric role. `goal` drives the decision, `guardrail` blocks ship if degraded, `secondary` is informational."
83
118
  )
84
119
  })
@@ -86,76 +121,81 @@ var experimentMetricsUpdateSchema = z.object({
86
121
  }).describe(
87
122
  "Body for `POST /api/admin/experiments/{id}/metrics`. Replaces the experiment's metric attachments wholesale. Returns `422` if any `metric_id` is unknown."
88
123
  );
89
- var experimentResponseSchema = z.object({
90
- id: z.string().describe("Stable opaque experiment id (`exp_\u2026`)."),
124
+ var experimentResponseSchema = z3.object({
125
+ id: z3.string().describe("Stable opaque experiment id (`exp_\u2026`)."),
91
126
  name: experimentNameSchema,
92
- description: z.string().nullable(),
93
- tag: z.string().nullable(),
94
- status: z.enum(["draft", "running", "stopped", "archived"]),
95
- universe: z.string().describe("Universe name this experiment draws from."),
96
- targetingGate: z.string().nullable(),
97
- allocationPct: z.number().int().describe("Allocation in basis points (0\u201310000)."),
98
- salt: z.string(),
99
- params: z.record(z.string(), z.enum(["string", "bool", "number"])),
100
- groups: z.array(experimentGroupSchema),
101
- significanceThreshold: z.number(),
102
- minRuntimeDays: z.number().int(),
103
- minSampleSize: z.number().int(),
104
- sequentialTesting: z.boolean(),
105
- startedAt: z.string().nullable().describe("ISO-8601 timestamp the experiment last transitioned to `running`, or `null`."),
106
- stoppedAt: z.string().nullable().optional(),
107
- updatedAt: z.string()
127
+ description: z3.string().nullable(),
128
+ hypothesis: z3.string().nullable(),
129
+ tag: z3.string().nullable(),
130
+ ownerEmail: z3.string().nullable(),
131
+ audience: z3.string().nullable(),
132
+ bucketBy: bucketBySchema.nullable(),
133
+ folder: z3.string().nullable(),
134
+ status: z3.enum(["draft", "running", "stopped", "archived"]),
135
+ universe: z3.string().describe("Universe name this experiment draws from."),
136
+ targetingGate: z3.string().nullable(),
137
+ allocationPct: z3.number().int().describe("Allocation in basis points (0\u201310000)."),
138
+ salt: z3.string(),
139
+ params: z3.record(z3.string(), z3.enum(["string", "bool", "number"])),
140
+ groups: z3.array(experimentGroupSchema),
141
+ significanceThreshold: z3.number(),
142
+ minRuntimeDays: z3.number().int(),
143
+ minSampleSize: z3.number().int(),
144
+ sequentialTesting: z3.boolean(),
145
+ startedAt: z3.string().nullable().describe("ISO-8601 timestamp the experiment last transitioned to `running`, or `null`."),
146
+ stoppedAt: z3.string().nullable().optional(),
147
+ updatedAt: z3.string()
108
148
  });
109
- var experimentCreateResponseSchema = z.object({
110
- id: z.string().describe("Newly assigned experiment id."),
149
+ var experimentCreateResponseSchema = z3.object({
150
+ id: z3.string().describe("Newly assigned experiment id."),
111
151
  name: experimentNameSchema
112
152
  });
113
- var experimentUpdateResponseSchema = z.object({
114
- id: z.string().describe("Experiment id that was updated.")
153
+ var experimentUpdateResponseSchema = z3.object({
154
+ id: z3.string().describe("Experiment id that was updated.")
115
155
  });
116
- var experimentDeleteResponseSchema = z.object({ ok: z.literal(true) });
117
- var experimentStatusResponseSchema = z.object({
118
- id: z.string(),
119
- status: z.enum(["draft", "running", "stopped", "archived"])
156
+ var experimentDeleteResponseSchema = z3.object({ ok: z3.literal(true) });
157
+ var experimentStatusResponseSchema = z3.object({
158
+ id: z3.string(),
159
+ status: z3.enum(["draft", "running", "stopped", "archived"])
120
160
  });
121
- var experimentMetricsResponseSchema = z.object({
122
- id: z.string(),
123
- metrics: z.array(
124
- z.object({
125
- metric_id: z.string(),
126
- role: z.enum(["goal", "guardrail", "secondary"])
161
+ var experimentMetricsResponseSchema = z3.object({
162
+ id: z3.string(),
163
+ metrics: z3.array(
164
+ z3.object({
165
+ metric_id: z3.string(),
166
+ role: z3.enum(["goal", "guardrail", "secondary"])
127
167
  })
128
168
  )
129
169
  });
130
- var experimentResultRowSchema = z.object({
131
- metric: z.string(),
132
- group_name: z.string(),
133
- ds: z.string().describe("Date slice (`YYYY-MM-DD`)."),
134
- n: z.number().nullable(),
135
- mean: z.number().nullable(),
136
- delta_pct: z.number().nullable(),
137
- p_value: z.number().nullable(),
138
- srm_detected: z.number().nullable().describe("`1` if sample-ratio mismatch detected, else `0`.")
170
+ var experimentResultRowSchema = z3.object({
171
+ metric: z3.string(),
172
+ group_name: z3.string(),
173
+ ds: z3.string().describe("Date slice (`YYYY-MM-DD`)."),
174
+ n: z3.number().nullable(),
175
+ mean: z3.number().nullable(),
176
+ delta_pct: z3.number().nullable(),
177
+ p_value: z3.number().nullable(),
178
+ srm_detected: z3.number().nullable().describe("`1` if sample-ratio mismatch detected, else `0`.")
139
179
  });
140
- var experimentResultsResponseSchema = z.object({
141
- experiment: z.object({
142
- id: z.string(),
180
+ var experimentResultsResponseSchema = z3.object({
181
+ experiment: z3.object({
182
+ id: z3.string(),
143
183
  name: experimentNameSchema,
144
- status: z.enum(["draft", "running", "stopped", "archived"])
184
+ status: z3.enum(["draft", "running", "stopped", "archived"])
145
185
  }),
146
- results: z.array(experimentResultRowSchema)
186
+ results: z3.array(experimentResultRowSchema)
147
187
  });
148
- var experimentTimeseriesResponseSchema = z.object({
149
- experiment: z.object({
150
- id: z.string(),
188
+ var experimentTimeseriesResponseSchema = z3.object({
189
+ experiment: z3.object({
190
+ id: z3.string(),
151
191
  name: experimentNameSchema,
152
- status: z.enum(["draft", "running", "stopped", "archived"])
192
+ status: z3.enum(["draft", "running", "stopped", "archived"])
153
193
  }),
154
- series: z.array(experimentResultRowSchema)
194
+ series: z3.array(experimentResultRowSchema)
155
195
  });
156
- var experimentReanalyzeResponseSchema = z.object({
157
- id: z.string(),
158
- queued: z.literal(true)
196
+ var experimentReanalyzeResponseSchema = z3.object({
197
+ id: z3.string(),
198
+ queued: z3.literal(true)
159
199
  });
160
200
 
161
201
  // src/transport.ts
@@ -169,9 +209,9 @@ var ApiError = class extends Error {
169
209
  };
170
210
 
171
211
  // src/resources/experiments.ts
172
- var experimentListResponseSchema = z2.object({
173
- data: z2.array(experimentResponseSchema),
174
- next_cursor: z2.string().nullable()
212
+ var experimentListResponseSchema = z4.object({
213
+ data: z4.array(experimentResponseSchema),
214
+ next_cursor: z4.string().nullable()
175
215
  });
176
216
  var BASE = "/api/admin/experiments";
177
217
  function experimentsClient(t) {
@@ -181,24 +221,29 @@ function experimentsClient(t) {
181
221
  if (opts.cursor) query.cursor = opts.cursor;
182
222
  return t.request("GET", BASE, void 0, query);
183
223
  }
184
- async function listAll() {
224
+ async function listAllWith(status) {
185
225
  const out = [];
186
226
  let cursor;
187
227
  do {
188
- const page = await list({ limit: 500, cursor });
228
+ const query = { limit: "500" };
229
+ if (cursor) query.cursor = cursor;
230
+ if (status) query.status = status;
231
+ const page = await t.request("GET", BASE, void 0, query);
189
232
  out.push(...page.data);
190
233
  cursor = page.next_cursor ?? void 0;
191
234
  } while (cursor);
192
235
  return out;
193
236
  }
237
+ async function listAll() {
238
+ return listAllWith();
239
+ }
194
240
  async function resolve(idOrName) {
195
241
  try {
196
242
  return await t.request("GET", `${BASE}/${idOrName}`);
197
243
  } catch (err) {
198
244
  if (!(err instanceof ApiError) || err.status !== 404) throw err;
199
245
  }
200
- const all = await listAll();
201
- const found = all.find((e) => e.name === idOrName);
246
+ const found = (await listAllWith()).find((e) => e.name === idOrName) ?? (await listAllWith("archived")).find((e) => e.name === idOrName);
202
247
  if (!found) throw new ApiError(`Experiment '${idOrName}' not found`, 404);
203
248
  return found;
204
249
  }
@@ -221,6 +266,7 @@ function experimentsClient(t) {
221
266
  start: (id) => setStatus(id, "running"),
222
267
  stop: (id) => setStatus(id, "stopped"),
223
268
  archive: (id) => setStatus(id, "archived"),
269
+ restore: (id) => setStatus(id, "draft"),
224
270
  setMetrics: (id, input) => t.request("POST", `${BASE}/${id}/metrics`, experimentMetricsUpdateSchema.parse(input)),
225
271
  results: (id) => t.request("GET", `${BASE}/${id}/results`),
226
272
  timeseries: (id, metric) => t.request(
@@ -267,7 +313,7 @@ var experimentsResource = {
267
313
  "",
268
314
  "**Identity.** Stable `name` (a-z, 0-9, `_`/`-`, max 64 chars). Immutable after create.",
269
315
  "",
270
- "**Lifecycle.** `draft \u2192 running \u2192 stopped \u2192 archived`. Transition via `POST /{id}/status`. Restarting an archived experiment is not allowed.",
316
+ "**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.",
271
317
  "",
272
318
  "**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.",
273
319
  "",
@@ -286,6 +332,7 @@ var experimentsResource = {
286
332
  { name: "start", description: "Transition draft experiment to running" },
287
333
  { name: "stop", description: "Stop a running experiment" },
288
334
  { name: "archive", description: "Archive a stopped experiment" },
335
+ { name: "restore", description: "Restore an archived (never-started) experiment to draft" },
289
336
  { name: "reanalyze", description: "Re-run analysis pass for the experiment" }
290
337
  ],
291
338
  endpoints: [
@@ -466,8 +513,9 @@ var experimentsResource = {
466
513
  "- `running \u2192 stopped` \u2014 halts allocation. Existing exposures stay in the dataset.",
467
514
  "- `stopped \u2192 archived` \u2014 soft-delete.",
468
515
  "- `draft \u2192 archived` \u2014 discard an unstarted experiment.",
516
+ "- `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.",
469
517
  "",
470
- "Restarting an `archived` experiment is not allowed; clone instead. Returns `409` on illegal transitions and `429` if the plan's `experiments_running` limit is exceeded on `\u2192 running`."
518
+ "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`."
471
519
  ].join("\n"),
472
520
  pathParams: { id: "Stable opaque experiment id." },
473
521
  request: experimentStatusUpdateSchema,
@@ -488,6 +536,11 @@ var experimentsResource = {
488
536
  summary: "Archive \u2014 stopped \u2192 archived",
489
537
  description: "Soft-deletes the experiment. List endpoints exclude archived rows by default.",
490
538
  value: { status: "archived" }
539
+ },
540
+ restore: {
541
+ summary: "Restore \u2014 archived \u2192 draft",
542
+ description: "Un-deletes a soft-deleted experiment so it can be re-completed and started. Allowed only if it never ran.",
543
+ value: { status: "draft" }
491
544
  }
492
545
  },
493
546
  response: { id: "exp_01j7wb12c3d4e5f6g7h8j9k0l1", status: "running" }
@@ -592,7 +645,7 @@ var experimentsResource = {
592
645
  pathParams: { id: "Stable opaque experiment id." },
593
646
  queryParams: {
594
647
  metric: {
595
- schema: z2.string().optional(),
648
+ schema: z4.string().optional(),
596
649
  description: "Optional metric name to filter the series."
597
650
  }
598
651
  },