@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.
@@ -29,12 +29,14 @@ var gateRuleSchema = z2.object({
29
29
  "semver_gt",
30
30
  "semver_gte",
31
31
  "semver_lt",
32
- "semver_lte"
32
+ "semver_lte",
33
+ "gate_pass",
34
+ "exp_in"
33
35
  ]).describe(
34
- "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)."
36
+ '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).'
35
37
  ),
36
38
  value: z2.unknown().describe(
37
- "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`."
39
+ '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`.'
38
40
  )
39
41
  }).describe(
40
42
  "Single targeting predicate. A gate's `rules` array is evaluated with AND semantics; use the gatekeeper `stack` for OR / fall-through logic."
@@ -45,9 +47,17 @@ var gateNameSchema = z2.string().regex(
45
47
  ).max(128).describe(
46
48
  "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."
47
49
  );
50
+ var rampSchema = z2.object({
51
+ from: z2.number().int().min(0).max(1e4).describe("Start of the ramp in basis points (0\u201310000)."),
52
+ to: z2.number().int().min(0).max(1e4).describe("End of the ramp in basis points (0\u201310000)."),
53
+ startAt: z2.number().int().nonnegative().describe("Ramp start, epoch milliseconds (UTC)."),
54
+ durationMs: z2.number().int().positive().describe("Ramp length in milliseconds.")
55
+ }).describe(
56
+ "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."
57
+ );
48
58
  var stackedConditionSchema = z2.object({
49
59
  id: z2.string().min(1).describe(
50
- "Client-supplied stable id for the stack entry (used for React keys, audit diffs)."
60
+ "Client-supplied stable id for the stack entry (used for React keys, audit diffs, and as the default per-condition rollout salt)."
51
61
  ),
52
62
  type: z2.literal("condition").describe("Discriminator. `condition` = rule-based predicate sub-gate."),
53
63
  name: z2.string().optional().describe("Human label shown in the dashboard."),
@@ -56,9 +66,15 @@ var stackedConditionSchema = z2.object({
56
66
  ),
57
67
  pass: z2.enum(["all", "any"]).optional().describe("Combinator across `rules`. `all` = AND (default), `any` = OR."),
58
68
  rules: z2.array(gateRuleSchema).default([]).describe("Predicates evaluated under `pass`. Empty array = match nothing."),
69
+ rolloutPct: z2.number().int().min(0).max(1e4).optional().describe(
70
+ "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)."
71
+ ),
72
+ bucketBy: z2.string().optional().describe("Attribute used to hash the caller into the per-condition bucket. Default `userID`."),
73
+ salt: z2.string().optional().describe("Per-condition hash salt. Defaults to the entry `id`, then the gate salt."),
74
+ ramp: rampSchema.optional(),
59
75
  locked: z2.boolean().optional().describe("If `true`, dashboard hides edit controls (e.g. the trailing public-floor entry).")
60
76
  }).describe(
61
- "Rule-based sub-gate. When matched, evaluation short-circuits and the gate returns `true` for the current caller."
77
+ "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."
62
78
  );
63
79
  var stackedRolloutSchema = z2.object({
64
80
  id: z2.string().min(1).describe("Client-supplied stable id for the stack entry."),
@@ -74,9 +90,10 @@ var stackedRolloutSchema = z2.object({
74
90
  salt: z2.string().optional().describe(
75
91
  "Per-entry hash salt. Defaults to the gate's salt. Set explicitly to keep two rollouts independent or correlated across gates."
76
92
  ),
93
+ ramp: rampSchema.optional(),
77
94
  locked: z2.boolean().optional().describe("If `true`, dashboard hides edit controls.")
78
95
  }).describe(
79
- "Percentage-rollout sub-gate. Hashes `bucketBy` (default `userID`) with `salt` and returns `true` for `rolloutPct/10000` fraction of callers."
96
+ "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)."
80
97
  );
81
98
  var stackedGateEntrySchema = z2.discriminatedUnion("type", [stackedConditionSchema, stackedRolloutSchema]).describe(
82
99
  "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."
@@ -80,6 +80,8 @@ declare const RESOURCE_REGISTRY: readonly [{
80
80
  semver_gte: "semver_gte";
81
81
  semver_lt: "semver_lt";
82
82
  semver_lte: "semver_lte";
83
+ gate_pass: "gate_pass";
84
+ exp_in: "exp_in";
83
85
  }>;
84
86
  value: zod.ZodUnknown;
85
87
  }, zod_v4_core.$strip>>>;
@@ -110,9 +112,20 @@ declare const RESOURCE_REGISTRY: readonly [{
110
112
  semver_gte: "semver_gte";
111
113
  semver_lt: "semver_lt";
112
114
  semver_lte: "semver_lte";
115
+ gate_pass: "gate_pass";
116
+ exp_in: "exp_in";
113
117
  }>;
114
118
  value: zod.ZodUnknown;
115
119
  }, zod_v4_core.$strip>>>;
120
+ rolloutPct: zod.ZodOptional<zod.ZodNumber>;
121
+ bucketBy: zod.ZodOptional<zod.ZodString>;
122
+ salt: zod.ZodOptional<zod.ZodString>;
123
+ ramp: zod.ZodOptional<zod.ZodObject<{
124
+ from: zod.ZodNumber;
125
+ to: zod.ZodNumber;
126
+ startAt: zod.ZodNumber;
127
+ durationMs: zod.ZodNumber;
128
+ }, zod_v4_core.$strip>>;
116
129
  locked: zod.ZodOptional<zod.ZodBoolean>;
117
130
  }, zod_v4_core.$strip>, zod.ZodObject<{
118
131
  id: zod.ZodString;
@@ -122,6 +135,12 @@ declare const RESOURCE_REGISTRY: readonly [{
122
135
  rolloutPct: zod.ZodNumber;
123
136
  bucketBy: zod.ZodOptional<zod.ZodString>;
124
137
  salt: zod.ZodOptional<zod.ZodString>;
138
+ ramp: zod.ZodOptional<zod.ZodObject<{
139
+ from: zod.ZodNumber;
140
+ to: zod.ZodNumber;
141
+ startAt: zod.ZodNumber;
142
+ durationMs: zod.ZodNumber;
143
+ }, zod_v4_core.$strip>>;
125
144
  locked: zod.ZodOptional<zod.ZodBoolean>;
126
145
  }, zod_v4_core.$strip>], "type">>>>;
127
146
  }, zod_v4_core.$strip>;
@@ -150,6 +169,8 @@ declare const RESOURCE_REGISTRY: readonly [{
150
169
  semver_gte: "semver_gte";
151
170
  semver_lt: "semver_lt";
152
171
  semver_lte: "semver_lte";
172
+ gate_pass: "gate_pass";
173
+ exp_in: "exp_in";
153
174
  }>;
154
175
  value: zod.ZodUnknown;
155
176
  }, zod_v4_core.$strip>>>;
@@ -180,9 +201,20 @@ declare const RESOURCE_REGISTRY: readonly [{
180
201
  semver_gte: "semver_gte";
181
202
  semver_lt: "semver_lt";
182
203
  semver_lte: "semver_lte";
204
+ gate_pass: "gate_pass";
205
+ exp_in: "exp_in";
183
206
  }>;
184
207
  value: zod.ZodUnknown;
185
208
  }, zod_v4_core.$strip>>>;
209
+ rolloutPct: zod.ZodOptional<zod.ZodNumber>;
210
+ bucketBy: zod.ZodOptional<zod.ZodString>;
211
+ salt: zod.ZodOptional<zod.ZodString>;
212
+ ramp: zod.ZodOptional<zod.ZodObject<{
213
+ from: zod.ZodNumber;
214
+ to: zod.ZodNumber;
215
+ startAt: zod.ZodNumber;
216
+ durationMs: zod.ZodNumber;
217
+ }, zod_v4_core.$strip>>;
186
218
  locked: zod.ZodOptional<zod.ZodBoolean>;
187
219
  }, zod_v4_core.$strip>, zod.ZodObject<{
188
220
  id: zod.ZodString;
@@ -192,6 +224,12 @@ declare const RESOURCE_REGISTRY: readonly [{
192
224
  rolloutPct: zod.ZodNumber;
193
225
  bucketBy: zod.ZodOptional<zod.ZodString>;
194
226
  salt: zod.ZodOptional<zod.ZodString>;
227
+ ramp: zod.ZodOptional<zod.ZodObject<{
228
+ from: zod.ZodNumber;
229
+ to: zod.ZodNumber;
230
+ startAt: zod.ZodNumber;
231
+ durationMs: zod.ZodNumber;
232
+ }, zod_v4_core.$strip>>;
195
233
  locked: zod.ZodOptional<zod.ZodBoolean>;
196
234
  }, zod_v4_core.$strip>], "type">>>>;
197
235
  }, zod_v4_core.$strip>;
@@ -232,6 +270,8 @@ declare const RESOURCE_REGISTRY: readonly [{
232
270
  semver_gte: "semver_gte";
233
271
  semver_lt: "semver_lt";
234
272
  semver_lte: "semver_lte";
273
+ gate_pass: "gate_pass";
274
+ exp_in: "exp_in";
235
275
  }>;
236
276
  value: zod.ZodUnknown;
237
277
  }, zod_v4_core.$strip>>>;
@@ -267,9 +307,20 @@ declare const RESOURCE_REGISTRY: readonly [{
267
307
  semver_gte: "semver_gte";
268
308
  semver_lt: "semver_lt";
269
309
  semver_lte: "semver_lte";
310
+ gate_pass: "gate_pass";
311
+ exp_in: "exp_in";
270
312
  }>;
271
313
  value: zod.ZodUnknown;
272
314
  }, zod_v4_core.$strip>>>;
315
+ rolloutPct: zod.ZodOptional<zod.ZodNumber>;
316
+ bucketBy: zod.ZodOptional<zod.ZodString>;
317
+ salt: zod.ZodOptional<zod.ZodString>;
318
+ ramp: zod.ZodOptional<zod.ZodObject<{
319
+ from: zod.ZodNumber;
320
+ to: zod.ZodNumber;
321
+ startAt: zod.ZodNumber;
322
+ durationMs: zod.ZodNumber;
323
+ }, zod_v4_core.$strip>>;
273
324
  locked: zod.ZodOptional<zod.ZodBoolean>;
274
325
  }, zod_v4_core.$strip>, zod.ZodObject<{
275
326
  id: zod.ZodString;
@@ -279,6 +330,12 @@ declare const RESOURCE_REGISTRY: readonly [{
279
330
  rolloutPct: zod.ZodNumber;
280
331
  bucketBy: zod.ZodOptional<zod.ZodString>;
281
332
  salt: zod.ZodOptional<zod.ZodString>;
333
+ ramp: zod.ZodOptional<zod.ZodObject<{
334
+ from: zod.ZodNumber;
335
+ to: zod.ZodNumber;
336
+ startAt: zod.ZodNumber;
337
+ durationMs: zod.ZodNumber;
338
+ }, zod_v4_core.$strip>>;
282
339
  locked: zod.ZodOptional<zod.ZodBoolean>;
283
340
  }, zod_v4_core.$strip>], "type">>>>;
284
341
  updatedAt: zod.ZodString;
@@ -348,6 +405,8 @@ declare const RESOURCE_REGISTRY: readonly [{
348
405
  semver_gte: "semver_gte";
349
406
  semver_lt: "semver_lt";
350
407
  semver_lte: "semver_lte";
408
+ gate_pass: "gate_pass";
409
+ exp_in: "exp_in";
351
410
  }>;
352
411
  value: zod.ZodUnknown;
353
412
  }, zod_v4_core.$strip>>>;
@@ -378,9 +437,20 @@ declare const RESOURCE_REGISTRY: readonly [{
378
437
  semver_gte: "semver_gte";
379
438
  semver_lt: "semver_lt";
380
439
  semver_lte: "semver_lte";
440
+ gate_pass: "gate_pass";
441
+ exp_in: "exp_in";
381
442
  }>;
382
443
  value: zod.ZodUnknown;
383
444
  }, zod_v4_core.$strip>>>;
445
+ rolloutPct: zod.ZodOptional<zod.ZodNumber>;
446
+ bucketBy: zod.ZodOptional<zod.ZodString>;
447
+ salt: zod.ZodOptional<zod.ZodString>;
448
+ ramp: zod.ZodOptional<zod.ZodObject<{
449
+ from: zod.ZodNumber;
450
+ to: zod.ZodNumber;
451
+ startAt: zod.ZodNumber;
452
+ durationMs: zod.ZodNumber;
453
+ }, zod_v4_core.$strip>>;
384
454
  locked: zod.ZodOptional<zod.ZodBoolean>;
385
455
  }, zod_v4_core.$strip>, zod.ZodObject<{
386
456
  id: zod.ZodString;
@@ -390,6 +460,12 @@ declare const RESOURCE_REGISTRY: readonly [{
390
460
  rolloutPct: zod.ZodNumber;
391
461
  bucketBy: zod.ZodOptional<zod.ZodString>;
392
462
  salt: zod.ZodOptional<zod.ZodString>;
463
+ ramp: zod.ZodOptional<zod.ZodObject<{
464
+ from: zod.ZodNumber;
465
+ to: zod.ZodNumber;
466
+ startAt: zod.ZodNumber;
467
+ durationMs: zod.ZodNumber;
468
+ }, zod_v4_core.$strip>>;
393
469
  locked: zod.ZodOptional<zod.ZodBoolean>;
394
470
  }, zod_v4_core.$strip>], "type">>>>;
395
471
  }, zod_v4_core.$strip>;
@@ -515,6 +591,8 @@ declare const RESOURCE_REGISTRY: readonly [{
515
591
  semver_gte: "semver_gte";
516
592
  semver_lt: "semver_lt";
517
593
  semver_lte: "semver_lte";
594
+ gate_pass: "gate_pass";
595
+ exp_in: "exp_in";
518
596
  }>;
519
597
  value: zod.ZodUnknown;
520
598
  }, zod_v4_core.$strip>>>;
@@ -545,9 +623,20 @@ declare const RESOURCE_REGISTRY: readonly [{
545
623
  semver_gte: "semver_gte";
546
624
  semver_lt: "semver_lt";
547
625
  semver_lte: "semver_lte";
626
+ gate_pass: "gate_pass";
627
+ exp_in: "exp_in";
548
628
  }>;
549
629
  value: zod.ZodUnknown;
550
630
  }, zod_v4_core.$strip>>>;
631
+ rolloutPct: zod.ZodOptional<zod.ZodNumber>;
632
+ bucketBy: zod.ZodOptional<zod.ZodString>;
633
+ salt: zod.ZodOptional<zod.ZodString>;
634
+ ramp: zod.ZodOptional<zod.ZodObject<{
635
+ from: zod.ZodNumber;
636
+ to: zod.ZodNumber;
637
+ startAt: zod.ZodNumber;
638
+ durationMs: zod.ZodNumber;
639
+ }, zod_v4_core.$strip>>;
551
640
  locked: zod.ZodOptional<zod.ZodBoolean>;
552
641
  }, zod_v4_core.$strip>, zod.ZodObject<{
553
642
  id: zod.ZodString;
@@ -557,6 +646,12 @@ declare const RESOURCE_REGISTRY: readonly [{
557
646
  rolloutPct: zod.ZodNumber;
558
647
  bucketBy: zod.ZodOptional<zod.ZodString>;
559
648
  salt: zod.ZodOptional<zod.ZodString>;
649
+ ramp: zod.ZodOptional<zod.ZodObject<{
650
+ from: zod.ZodNumber;
651
+ to: zod.ZodNumber;
652
+ startAt: zod.ZodNumber;
653
+ durationMs: zod.ZodNumber;
654
+ }, zod_v4_core.$strip>>;
560
655
  locked: zod.ZodOptional<zod.ZodBoolean>;
561
656
  }, zod_v4_core.$strip>], "type">>>>;
562
657
  }, zod_v4_core.$strip>;
@@ -709,11 +804,7 @@ declare const RESOURCE_REGISTRY: readonly [{
709
804
  tag: zod.ZodDefault<zod.ZodNullable<zod.ZodString>>;
710
805
  owner_email: zod.ZodDefault<zod.ZodNullable<zod.ZodString>>;
711
806
  audience: zod.ZodDefault<zod.ZodNullable<zod.ZodString>>;
712
- bucket_by: zod.ZodDefault<zod.ZodNullable<zod.ZodEnum<{
713
- user_id: "user_id";
714
- session_id: "session_id";
715
- device_id: "device_id";
716
- }>>>;
807
+ bucket_by: zod.ZodDefault<zod.ZodNullable<zod.ZodString>>;
717
808
  folder: zod.ZodOptional<zod.ZodNullable<zod.ZodString>>;
718
809
  universe: zod.ZodString;
719
810
  targeting_gate: zod.ZodDefault<zod.ZodNullable<zod.ZodString>>;
@@ -749,11 +840,7 @@ declare const RESOURCE_REGISTRY: readonly [{
749
840
  tag: zod.ZodOptional<zod.ZodNullable<zod.ZodString>>;
750
841
  owner_email: zod.ZodOptional<zod.ZodNullable<zod.ZodString>>;
751
842
  audience: zod.ZodOptional<zod.ZodNullable<zod.ZodString>>;
752
- bucket_by: zod.ZodOptional<zod.ZodNullable<zod.ZodEnum<{
753
- user_id: "user_id";
754
- session_id: "session_id";
755
- device_id: "device_id";
756
- }>>>;
843
+ bucket_by: zod.ZodOptional<zod.ZodNullable<zod.ZodString>>;
757
844
  folder: zod.ZodOptional<zod.ZodNullable<zod.ZodString>>;
758
845
  targeting_gate: zod.ZodOptional<zod.ZodNullable<zod.ZodString>>;
759
846
  allocation_pct: zod.ZodOptional<zod.ZodNumber>;
@@ -792,6 +879,9 @@ declare const RESOURCE_REGISTRY: readonly [{
792
879
  }, {
793
880
  readonly name: "archive";
794
881
  readonly description: "Archive a stopped experiment";
882
+ }, {
883
+ readonly name: "restore";
884
+ readonly description: "Restore an archived (never-started) experiment to draft";
795
885
  }, {
796
886
  readonly name: "reanalyze";
797
887
  readonly description: "Re-run analysis pass for the experiment";
@@ -811,11 +901,7 @@ declare const RESOURCE_REGISTRY: readonly [{
811
901
  tag: zod.ZodNullable<zod.ZodString>;
812
902
  ownerEmail: zod.ZodNullable<zod.ZodString>;
813
903
  audience: zod.ZodNullable<zod.ZodString>;
814
- bucketBy: zod.ZodNullable<zod.ZodEnum<{
815
- user_id: "user_id";
816
- session_id: "session_id";
817
- device_id: "device_id";
818
- }>>;
904
+ bucketBy: zod.ZodNullable<zod.ZodString>;
819
905
  folder: zod.ZodNullable<zod.ZodString>;
820
906
  status: zod.ZodEnum<{
821
907
  draft: "draft";
@@ -898,11 +984,7 @@ declare const RESOURCE_REGISTRY: readonly [{
898
984
  tag: zod.ZodNullable<zod.ZodString>;
899
985
  ownerEmail: zod.ZodNullable<zod.ZodString>;
900
986
  audience: zod.ZodNullable<zod.ZodString>;
901
- bucketBy: zod.ZodNullable<zod.ZodEnum<{
902
- user_id: "user_id";
903
- session_id: "session_id";
904
- device_id: "device_id";
905
- }>>;
987
+ bucketBy: zod.ZodNullable<zod.ZodString>;
906
988
  folder: zod.ZodNullable<zod.ZodString>;
907
989
  status: zod.ZodEnum<{
908
990
  draft: "draft";
@@ -977,11 +1059,7 @@ declare const RESOURCE_REGISTRY: readonly [{
977
1059
  tag: zod.ZodDefault<zod.ZodNullable<zod.ZodString>>;
978
1060
  owner_email: zod.ZodDefault<zod.ZodNullable<zod.ZodString>>;
979
1061
  audience: zod.ZodDefault<zod.ZodNullable<zod.ZodString>>;
980
- bucket_by: zod.ZodDefault<zod.ZodNullable<zod.ZodEnum<{
981
- user_id: "user_id";
982
- session_id: "session_id";
983
- device_id: "device_id";
984
- }>>>;
1062
+ bucket_by: zod.ZodDefault<zod.ZodNullable<zod.ZodString>>;
985
1063
  folder: zod.ZodOptional<zod.ZodNullable<zod.ZodString>>;
986
1064
  universe: zod.ZodString;
987
1065
  targeting_gate: zod.ZodDefault<zod.ZodNullable<zod.ZodString>>;
@@ -1116,11 +1194,7 @@ declare const RESOURCE_REGISTRY: readonly [{
1116
1194
  tag: zod.ZodOptional<zod.ZodNullable<zod.ZodString>>;
1117
1195
  owner_email: zod.ZodOptional<zod.ZodNullable<zod.ZodString>>;
1118
1196
  audience: zod.ZodOptional<zod.ZodNullable<zod.ZodString>>;
1119
- bucket_by: zod.ZodOptional<zod.ZodNullable<zod.ZodEnum<{
1120
- user_id: "user_id";
1121
- session_id: "session_id";
1122
- device_id: "device_id";
1123
- }>>>;
1197
+ bucket_by: zod.ZodOptional<zod.ZodNullable<zod.ZodString>>;
1124
1198
  folder: zod.ZodOptional<zod.ZodNullable<zod.ZodString>>;
1125
1199
  targeting_gate: zod.ZodOptional<zod.ZodNullable<zod.ZodString>>;
1126
1200
  allocation_pct: zod.ZodOptional<zod.ZodNumber>;
@@ -1262,6 +1336,13 @@ declare const RESOURCE_REGISTRY: readonly [{
1262
1336
  readonly status: "archived";
1263
1337
  };
1264
1338
  };
1339
+ readonly restore: {
1340
+ readonly summary: "Restore — archived → draft";
1341
+ readonly description: "Un-deletes a soft-deleted experiment so it can be re-completed and started. Allowed only if it never ran.";
1342
+ readonly value: {
1343
+ readonly status: "draft";
1344
+ };
1345
+ };
1265
1346
  };
1266
1347
  readonly response: {
1267
1348
  readonly id: "exp_01j7wb12c3d4e5f6g7h8j9k0l1";
@@ -29,12 +29,14 @@ var gateRuleSchema = z2.object({
29
29
  "semver_gt",
30
30
  "semver_gte",
31
31
  "semver_lt",
32
- "semver_lte"
32
+ "semver_lte",
33
+ "gate_pass",
34
+ "exp_in"
33
35
  ]).describe(
34
- "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)."
36
+ '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).'
35
37
  ),
36
38
  value: z2.unknown().describe(
37
- "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`."
39
+ '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`.'
38
40
  )
39
41
  }).describe(
40
42
  "Single targeting predicate. A gate's `rules` array is evaluated with AND semantics; use the gatekeeper `stack` for OR / fall-through logic."
@@ -45,9 +47,17 @@ var gateNameSchema = z2.string().regex(
45
47
  ).max(128).describe(
46
48
  "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."
47
49
  );
50
+ var rampSchema = z2.object({
51
+ from: z2.number().int().min(0).max(1e4).describe("Start of the ramp in basis points (0\u201310000)."),
52
+ to: z2.number().int().min(0).max(1e4).describe("End of the ramp in basis points (0\u201310000)."),
53
+ startAt: z2.number().int().nonnegative().describe("Ramp start, epoch milliseconds (UTC)."),
54
+ durationMs: z2.number().int().positive().describe("Ramp length in milliseconds.")
55
+ }).describe(
56
+ "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."
57
+ );
48
58
  var stackedConditionSchema = z2.object({
49
59
  id: z2.string().min(1).describe(
50
- "Client-supplied stable id for the stack entry (used for React keys, audit diffs)."
60
+ "Client-supplied stable id for the stack entry (used for React keys, audit diffs, and as the default per-condition rollout salt)."
51
61
  ),
52
62
  type: z2.literal("condition").describe("Discriminator. `condition` = rule-based predicate sub-gate."),
53
63
  name: z2.string().optional().describe("Human label shown in the dashboard."),
@@ -56,9 +66,15 @@ var stackedConditionSchema = z2.object({
56
66
  ),
57
67
  pass: z2.enum(["all", "any"]).optional().describe("Combinator across `rules`. `all` = AND (default), `any` = OR."),
58
68
  rules: z2.array(gateRuleSchema).default([]).describe("Predicates evaluated under `pass`. Empty array = match nothing."),
69
+ rolloutPct: z2.number().int().min(0).max(1e4).optional().describe(
70
+ "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)."
71
+ ),
72
+ bucketBy: z2.string().optional().describe("Attribute used to hash the caller into the per-condition bucket. Default `userID`."),
73
+ salt: z2.string().optional().describe("Per-condition hash salt. Defaults to the entry `id`, then the gate salt."),
74
+ ramp: rampSchema.optional(),
59
75
  locked: z2.boolean().optional().describe("If `true`, dashboard hides edit controls (e.g. the trailing public-floor entry).")
60
76
  }).describe(
61
- "Rule-based sub-gate. When matched, evaluation short-circuits and the gate returns `true` for the current caller."
77
+ "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."
62
78
  );
63
79
  var stackedRolloutSchema = z2.object({
64
80
  id: z2.string().min(1).describe("Client-supplied stable id for the stack entry."),
@@ -74,9 +90,10 @@ var stackedRolloutSchema = z2.object({
74
90
  salt: z2.string().optional().describe(
75
91
  "Per-entry hash salt. Defaults to the gate's salt. Set explicitly to keep two rollouts independent or correlated across gates."
76
92
  ),
93
+ ramp: rampSchema.optional(),
77
94
  locked: z2.boolean().optional().describe("If `true`, dashboard hides edit controls.")
78
95
  }).describe(
79
- "Percentage-rollout sub-gate. Hashes `bucketBy` (default `userID`) with `salt` and returns `true` for `rolloutPct/10000` fraction of callers."
96
+ "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)."
80
97
  );
81
98
  var stackedGateEntrySchema = z2.discriminatedUnion("type", [stackedConditionSchema, stackedRolloutSchema]).describe(
82
99
  "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."
@@ -530,7 +547,12 @@ var experimentGroupSchema = z5.object({
530
547
  var paramSchemaSchema = z5.record(z5.string(), z5.enum(["string", "bool", "number"])).describe(
531
548
  "Map of param-name \u2192 scalar type. Defines the shape of `groups[].params`. Example: `{ headline: 'string', show_cta: 'bool' }`."
532
549
  );
533
- var bucketBySchema = z5.enum(["user_id", "session_id", "device_id"]).describe("Identifier used as the bucketing key. `user_id` is sticky across sessions.");
550
+ var bucketBySchema = z5.string().min(1).max(128).regex(
551
+ /^[a-zA-Z_][a-zA-Z0-9_]*$/,
552
+ "Must be a user-attribute name (letters, digits, underscore; not starting with a digit)."
553
+ ).describe(
554
+ "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."
555
+ );
534
556
  var experimentCreateSchema = z5.object({
535
557
  name: experimentNameSchema,
536
558
  description: z5.string().max(2e3).nullable().default(null).describe("Free-form description. Max 2000 chars, markdown rendered in the dashboard."),
@@ -602,7 +624,7 @@ var experimentUpdateSchema = z5.object({
602
624
  );
603
625
  var experimentStatusUpdateSchema = z5.object({
604
626
  status: z5.enum(["draft", "running", "stopped", "archived"]).describe(
605
- "Target status. Allowed transitions: `draft \u2192 running`, `running \u2192 stopped`, `stopped \u2192 archived`, `draft \u2192 archived`. Restarting an archived experiment is not allowed."
627
+ "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."
606
628
  )
607
629
  }).describe("Body for `POST /api/admin/experiments/{id}/status`.");
608
630
  var experimentMetricsUpdateSchema = z5.object({
@@ -707,24 +729,29 @@ function experimentsClient(t) {
707
729
  if (opts.cursor) query.cursor = opts.cursor;
708
730
  return t.request("GET", BASE2, void 0, query);
709
731
  }
710
- async function listAll() {
732
+ async function listAllWith(status) {
711
733
  const out = [];
712
734
  let cursor;
713
735
  do {
714
- const page = await list({ limit: 500, cursor });
736
+ const query = { limit: "500" };
737
+ if (cursor) query.cursor = cursor;
738
+ if (status) query.status = status;
739
+ const page = await t.request("GET", BASE2, void 0, query);
715
740
  out.push(...page.data);
716
741
  cursor = page.next_cursor ?? void 0;
717
742
  } while (cursor);
718
743
  return out;
719
744
  }
745
+ async function listAll() {
746
+ return listAllWith();
747
+ }
720
748
  async function resolve(idOrName) {
721
749
  try {
722
750
  return await t.request("GET", `${BASE2}/${idOrName}`);
723
751
  } catch (err) {
724
752
  if (!(err instanceof ApiError) || err.status !== 404) throw err;
725
753
  }
726
- const all = await listAll();
727
- const found = all.find((e) => e.name === idOrName);
754
+ const found = (await listAllWith()).find((e) => e.name === idOrName) ?? (await listAllWith("archived")).find((e) => e.name === idOrName);
728
755
  if (!found) throw new ApiError(`Experiment '${idOrName}' not found`, 404);
729
756
  return found;
730
757
  }
@@ -747,6 +774,7 @@ function experimentsClient(t) {
747
774
  start: (id) => setStatus(id, "running"),
748
775
  stop: (id) => setStatus(id, "stopped"),
749
776
  archive: (id) => setStatus(id, "archived"),
777
+ restore: (id) => setStatus(id, "draft"),
750
778
  setMetrics: (id, input) => t.request("POST", `${BASE2}/${id}/metrics`, experimentMetricsUpdateSchema.parse(input)),
751
779
  results: (id) => t.request("GET", `${BASE2}/${id}/results`),
752
780
  timeseries: (id, metric) => t.request(
@@ -793,7 +821,7 @@ var experimentsResource = {
793
821
  "",
794
822
  "**Identity.** Stable `name` (a-z, 0-9, `_`/`-`, max 64 chars). Immutable after create.",
795
823
  "",
796
- "**Lifecycle.** `draft \u2192 running \u2192 stopped \u2192 archived`. Transition via `POST /{id}/status`. Restarting an archived experiment is not allowed.",
824
+ "**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.",
797
825
  "",
798
826
  "**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.",
799
827
  "",
@@ -812,6 +840,7 @@ var experimentsResource = {
812
840
  { name: "start", description: "Transition draft experiment to running" },
813
841
  { name: "stop", description: "Stop a running experiment" },
814
842
  { name: "archive", description: "Archive a stopped experiment" },
843
+ { name: "restore", description: "Restore an archived (never-started) experiment to draft" },
815
844
  { name: "reanalyze", description: "Re-run analysis pass for the experiment" }
816
845
  ],
817
846
  endpoints: [
@@ -992,8 +1021,9 @@ var experimentsResource = {
992
1021
  "- `running \u2192 stopped` \u2014 halts allocation. Existing exposures stay in the dataset.",
993
1022
  "- `stopped \u2192 archived` \u2014 soft-delete.",
994
1023
  "- `draft \u2192 archived` \u2014 discard an unstarted experiment.",
1024
+ "- `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.",
995
1025
  "",
996
- "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`."
1026
+ "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`."
997
1027
  ].join("\n"),
998
1028
  pathParams: { id: "Stable opaque experiment id." },
999
1029
  request: experimentStatusUpdateSchema,
@@ -1014,6 +1044,11 @@ var experimentsResource = {
1014
1044
  summary: "Archive \u2014 stopped \u2192 archived",
1015
1045
  description: "Soft-deletes the experiment. List endpoints exclude archived rows by default.",
1016
1046
  value: { status: "archived" }
1047
+ },
1048
+ restore: {
1049
+ summary: "Restore \u2014 archived \u2192 draft",
1050
+ description: "Un-deletes a soft-deleted experiment so it can be re-completed and started. Allowed only if it never ran.",
1051
+ value: { status: "draft" }
1017
1052
  }
1018
1053
  },
1019
1054
  response: { id: "exp_01j7wb12c3d4e5f6g7h8j9k0l1", status: "running" }
@@ -16,11 +16,7 @@ declare const paramSchemaSchema: z.ZodRecord<z.ZodString, z.ZodEnum<{
16
16
  number: "number";
17
17
  bool: "bool";
18
18
  }>>;
19
- declare const bucketBySchema: z.ZodEnum<{
20
- user_id: "user_id";
21
- session_id: "session_id";
22
- device_id: "device_id";
23
- }>;
19
+ declare const bucketBySchema: z.ZodString;
24
20
  declare const experimentCreateSchema: z.ZodObject<{
25
21
  name: z.ZodString;
26
22
  description: z.ZodDefault<z.ZodNullable<z.ZodString>>;
@@ -28,11 +24,7 @@ declare const experimentCreateSchema: z.ZodObject<{
28
24
  tag: z.ZodDefault<z.ZodNullable<z.ZodString>>;
29
25
  owner_email: z.ZodDefault<z.ZodNullable<z.ZodString>>;
30
26
  audience: z.ZodDefault<z.ZodNullable<z.ZodString>>;
31
- bucket_by: z.ZodDefault<z.ZodNullable<z.ZodEnum<{
32
- user_id: "user_id";
33
- session_id: "session_id";
34
- device_id: "device_id";
35
- }>>>;
27
+ bucket_by: z.ZodDefault<z.ZodNullable<z.ZodString>>;
36
28
  folder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
37
29
  universe: z.ZodString;
38
30
  targeting_gate: z.ZodDefault<z.ZodNullable<z.ZodString>>;
@@ -68,11 +60,7 @@ declare const experimentUpdateSchema: z.ZodObject<{
68
60
  tag: z.ZodOptional<z.ZodNullable<z.ZodString>>;
69
61
  owner_email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
70
62
  audience: z.ZodOptional<z.ZodNullable<z.ZodString>>;
71
- bucket_by: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
72
- user_id: "user_id";
73
- session_id: "session_id";
74
- device_id: "device_id";
75
- }>>>;
63
+ bucket_by: z.ZodOptional<z.ZodNullable<z.ZodString>>;
76
64
  folder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
77
65
  targeting_gate: z.ZodOptional<z.ZodNullable<z.ZodString>>;
78
66
  allocation_pct: z.ZodOptional<z.ZodNumber>;
@@ -127,11 +115,7 @@ declare const experimentResponseSchema: z.ZodObject<{
127
115
  tag: z.ZodNullable<z.ZodString>;
128
116
  ownerEmail: z.ZodNullable<z.ZodString>;
129
117
  audience: z.ZodNullable<z.ZodString>;
130
- bucketBy: z.ZodNullable<z.ZodEnum<{
131
- user_id: "user_id";
132
- session_id: "session_id";
133
- device_id: "device_id";
134
- }>>;
118
+ bucketBy: z.ZodNullable<z.ZodString>;
135
119
  folder: z.ZodNullable<z.ZodString>;
136
120
  status: z.ZodEnum<{
137
121
  draft: "draft";
@@ -31,7 +31,12 @@ var experimentGroupSchema = z3.object({
31
31
  var paramSchemaSchema = z3.record(z3.string(), z3.enum(["string", "bool", "number"])).describe(
32
32
  "Map of param-name \u2192 scalar type. Defines the shape of `groups[].params`. Example: `{ headline: 'string', show_cta: 'bool' }`."
33
33
  );
34
- var bucketBySchema = z3.enum(["user_id", "session_id", "device_id"]).describe("Identifier used as the bucketing key. `user_id` is sticky across sessions.");
34
+ var bucketBySchema = z3.string().min(1).max(128).regex(
35
+ /^[a-zA-Z_][a-zA-Z0-9_]*$/,
36
+ "Must be a user-attribute name (letters, digits, underscore; not starting with a digit)."
37
+ ).describe(
38
+ "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."
39
+ );
35
40
  var experimentCreateSchema = z3.object({
36
41
  name: experimentNameSchema,
37
42
  description: z3.string().max(2e3).nullable().default(null).describe("Free-form description. Max 2000 chars, markdown rendered in the dashboard."),
@@ -103,7 +108,7 @@ var experimentUpdateSchema = z3.object({
103
108
  );
104
109
  var experimentStatusUpdateSchema = z3.object({
105
110
  status: z3.enum(["draft", "running", "stopped", "archived"]).describe(
106
- "Target status. Allowed transitions: `draft \u2192 running`, `running \u2192 stopped`, `stopped \u2192 archived`, `draft \u2192 archived`. Restarting an archived experiment is not allowed."
111
+ "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."
107
112
  )
108
113
  }).describe("Body for `POST /api/admin/experiments/{id}/status`.");
109
114
  var experimentMetricsUpdateSchema = z3.object({