@shipeasy/openapi 3.1.0 → 3.3.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 (45) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-V4ZESAIF.js → chunk-75Z4SQOM.js} +141 -1
  3. package/dist/chunk-75Z4SQOM.js.map +1 -0
  4. package/dist/{chunk-UV333BIK.js → chunk-ZYK74ZIW.js} +569 -119
  5. package/dist/chunk-ZYK74ZIW.js.map +1 -0
  6. package/dist/{client-yqGYju-M.d.ts → client-DAGqRpF1.d.ts} +2209 -401
  7. package/dist/client.d.ts +1 -1
  8. package/dist/client.js +33 -1
  9. package/dist/custom.d.ts +5 -5
  10. package/dist/custom.js +128 -84
  11. package/dist/custom.js.map +1 -1
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.js +34 -2
  14. package/dist/index.js.map +1 -1
  15. package/dist/schemas.d.ts +2007 -891
  16. package/dist/schemas.js +179 -1
  17. package/openapi-sdk.json +1769 -0
  18. package/openapi.json +5861 -2597
  19. package/openapi.yaml +2739 -452
  20. package/package.json +6 -4
  21. package/spec/components/responses.yaml +39 -0
  22. package/spec/components/schemas/alert-rules.yaml +405 -7
  23. package/spec/components/schemas/alerts.yaml +9 -7
  24. package/spec/components/schemas/common.yaml +2 -2
  25. package/spec/components/schemas/connectors.yaml +69 -6
  26. package/spec/components/schemas/events.yaml +20 -9
  27. package/spec/components/schemas/gates.yaml +87 -0
  28. package/spec/components/schemas/keys.yaml +32 -2
  29. package/spec/components/schemas/killswitches.yaml +64 -0
  30. package/spec/components/schemas/metrics.yaml +131 -251
  31. package/spec/components/schemas/ops.yaml +555 -22
  32. package/spec/components/schemas/projects.yaml +18 -0
  33. package/spec/components/schemas/tags.yaml +91 -0
  34. package/spec/openapi-sdk.yaml +86 -0
  35. package/spec/openapi.yaml +61 -0
  36. package/spec/paths/alerts.yaml +5 -3
  37. package/spec/paths/gates.yaml +282 -0
  38. package/spec/paths/i18n.yaml +2 -1
  39. package/spec/paths/keys.yaml +79 -0
  40. package/spec/paths/killswitches.yaml +96 -0
  41. package/spec/paths/metrics.yaml +26 -17
  42. package/spec/paths/ops.yaml +566 -10
  43. package/spec/paths/tags.yaml +270 -0
  44. package/dist/chunk-UV333BIK.js.map +0 -1
  45. package/dist/chunk-V4ZESAIF.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipeasy/openapi",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "Shipeasy admin OpenAPI 3.2 spec (hand-authored, single source of truth) + the generated TypeScript client, Zod schemas, and types. Consumed by @shipeasy/cli and @shipeasy/mcp.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -40,17 +40,19 @@
40
40
  "default": "./src/codegen.ts"
41
41
  },
42
42
  "./openapi.yaml": "./openapi.yaml",
43
- "./openapi.json": "./openapi.json"
43
+ "./openapi.json": "./openapi.json",
44
+ "./openapi-sdk.json": "./openapi-sdk.json"
44
45
  },
45
46
  "files": [
46
47
  "dist/",
47
48
  "openapi.yaml",
48
49
  "openapi.json",
50
+ "openapi-sdk.json",
49
51
  "spec/"
50
52
  ],
51
53
  "scripts": {
52
- "lint:spec": "redocly lint spec/openapi.yaml",
53
- "bundle": "redocly bundle spec/openapi.yaml -o openapi.yaml && redocly bundle spec/openapi.yaml -o openapi.json",
54
+ "lint:spec": "redocly lint spec/openapi.yaml && redocly lint spec/openapi-sdk.yaml",
55
+ "bundle": "redocly bundle spec/openapi.yaml -o openapi.yaml && redocly bundle spec/openapi.yaml -o openapi.json && redocly bundle spec/openapi-sdk.yaml -o openapi-sdk.json",
54
56
  "gen:sdk": "openapi-ts",
55
57
  "gen": "pnpm bundle && pnpm gen:sdk",
56
58
  "prebuild": "pnpm gen",
@@ -120,3 +120,42 @@ BadGateway:
120
120
  summary: Downstream provider dispatch failed
121
121
  value:
122
122
  error: "GitHub API responded 403: resource not accessible by integration."
123
+ PayloadTooLarge:
124
+ description: The request body exceeded the intake's fixed size cap (16 KB).
125
+ content:
126
+ application/json:
127
+ schema:
128
+ $ref: ./errors.yaml#/Error
129
+ examples:
130
+ BAD_REQUEST:
131
+ summary: Body too large
132
+ value:
133
+ error: Body too large
134
+ code: BAD_REQUEST
135
+ TooManyRequests:
136
+ description: |-
137
+ The public intake is rate-limited per (project, client IP). Back off and retry;
138
+ the limit is a burst guard, not a quota, so a modest delay clears it.
139
+ content:
140
+ application/json:
141
+ schema:
142
+ $ref: ./errors.yaml#/Error
143
+ examples:
144
+ BAD_REQUEST:
145
+ summary: Rate limited
146
+ value:
147
+ error: Too many requests
148
+ code: BAD_REQUEST
149
+ ServiceUnavailable:
150
+ description: |-
151
+ A backing store the endpoint needs is not bound in this environment. Transient
152
+ and infrastructural — no stable `ErrorCode` is assigned, so `code` is omitted.
153
+ content:
154
+ application/json:
155
+ schema:
156
+ $ref: ./errors.yaml#/Error
157
+ examples:
158
+ UNAVAILABLE:
159
+ summary: Storage unbound
160
+ value:
161
+ error: Unavailable
@@ -1,3 +1,30 @@
1
+ AlertCompositeSpec:
2
+ type: object
3
+ description: >-
4
+ What a `composite` rule is a boolean over. `rules` are sibling alert-rule ids
5
+ in the same project and `op` is how their states combine. A child's state is
6
+ what the current pass concluded about it, falling back to whether it has an
7
+ open instance — so a composite still means something on a tick where a child
8
+ could not be evaluated, which is exactly the tick a "two of these are broken
9
+ at once" rule is for. One level deep: a child may not itself be composite.
10
+ properties:
11
+ op:
12
+ type: string
13
+ enum:
14
+ - and
15
+ - or
16
+ description: How the children's states combine.
17
+ rules:
18
+ type: array
19
+ minItems: 2
20
+ items:
21
+ type: string
22
+ minLength: 1
23
+ description: Ids of the alert rules to combine. Stored sorted and de-duplicated.
24
+ required:
25
+ - op
26
+ - rules
27
+ additionalProperties: false
1
28
  ListAlertRulesResponse:
2
29
  type: array
3
30
  items:
@@ -17,6 +44,26 @@ ListAlertRulesResponse:
17
44
  - type: string
18
45
  - type: "null"
19
46
  description: Display name of the bound metric, or `null` if the metric no longer exists.
47
+ kind:
48
+ type: string
49
+ enum:
50
+ - normal
51
+ - anomaly
52
+ - outliers
53
+ - no_data
54
+ - composite
55
+ description: >-
56
+ What the rule watches for. `normal` compares the metric's own value —
57
+ against `threshold` via `comparator`, or against the
58
+ [`rangeMin`, `rangeMax`] corridor it must stay inside. `anomaly`
59
+ compares it against ITS OWN seasonal baseline in sigmas, which is the
60
+ same fit a chart draws as the band. `outliers` compares each `by()`
61
+ group against its peers in the same bucket, so it needs a grouped
62
+ metric. `no_data` fires when the metric records nothing at all for
63
+ `noDataMinutes` — a count of zero is not silence, and the two are
64
+ distinguished by the reader's presence columns. `composite` is a
65
+ boolean over OTHER rules' current state and watches no metric of its
66
+ own.
20
67
  comparator:
21
68
  type: string
22
69
  enum:
@@ -24,15 +71,96 @@ ListAlertRulesResponse:
24
71
  - gte
25
72
  - lt
26
73
  - lte
27
- description: How the metric value is compared to the threshold (gt/gte/lt/lte).
74
+ description: How the metric value is compared to the threshold (gt/gte/lt/lte). Read only by a `normal` rule with no range set.
28
75
  threshold:
29
76
  type: number
30
- description: Threshold the metric value is compared against.
77
+ description: Threshold the metric value is compared against. Read only by a `normal` rule with no range set.
78
+ rangeMin:
79
+ anyOf:
80
+ - type: number
81
+ - type: "null"
82
+ description: Lower edge of the corridor a `normal` rule must stay inside; `null` if the rule is judged by comparator and threshold.
83
+ rangeMax:
84
+ anyOf:
85
+ - type: number
86
+ - type: "null"
87
+ description: Upper edge of the corridor a `normal` rule must stay inside; `null` if the rule is judged by comparator and threshold.
88
+ sigma:
89
+ anyOf:
90
+ - type: number
91
+ - type: "null"
92
+ description: How far from normal is too far, for `anomaly` and `outliers`, in sigma-equivalents. `null` takes the default of 3, which is also the width of the band a chart draws.
93
+ direction:
94
+ anyOf:
95
+ - type: string
96
+ enum:
97
+ - above
98
+ - below
99
+ - either
100
+ - type: "null"
101
+ description: Which side of the baseline an `anomaly` or `outliers` rule watches. `null` means either.
102
+ sustained:
103
+ type: boolean
104
+ description: >-
105
+ Whether the rule is judged by ACCUMULATED departure rather than bucket
106
+ by bucket. `anomaly` and `outliers` only.
107
+ warnThreshold:
108
+ anyOf:
109
+ - type: number
110
+ - type: "null"
111
+ description: The milder bound, in the same unit the kind judges in. `null` means the rule has one level.
112
+ recoveryThreshold:
113
+ anyOf:
114
+ - type: number
115
+ - type: "null"
116
+ description: What the metric must get back to before a live alert closes. `null` means no hysteresis — the firing bound clears it.
117
+ groupAlerts:
118
+ type: boolean
119
+ description: Whether the rule fires one alert per `by()` group rather than one for the whole metric.
120
+ maxGroups:
121
+ anyOf:
122
+ - type: integer
123
+ - type: "null"
124
+ description: How many groups this rule may alert on at once. `null` takes the shipped cap of 10.
125
+ noDataMinutes:
126
+ anyOf:
127
+ - type: integer
128
+ - type: "null"
129
+ description: How long the silence must last for a `no_data` rule. `null` on every other kind.
130
+ delayMinutes:
131
+ anyOf:
132
+ - type: integer
133
+ - type: "null"
134
+ description: How far behind live the evaluated window is held, on top of the reader's settle grace. `null` means no extra delay.
135
+ autoResolveMinutes:
136
+ anyOf:
137
+ - type: integer
138
+ - type: "null"
139
+ description: Close a live instance that has gone this long without a fresh verdict. `null` never auto-resolves.
140
+ composite:
141
+ anyOf:
142
+ - $ref: "#/AlertCompositeSpec"
143
+ - type: "null"
144
+ description: For a `composite` rule, the child rules and how their states combine. `null` on every other kind.
31
145
  windowHours:
32
146
  type: integer
33
147
  minimum: -9007199254740991
34
148
  maximum: 9007199254740991
35
149
  description: Lookback window (hours) the metric is aggregated over.
150
+ bucketMinutes:
151
+ anyOf:
152
+ - type: integer
153
+ - type: "null"
154
+ description: >-
155
+ Width of the buckets the window is split into, in minutes. `null`
156
+ divides the window into 12 equal buckets.
157
+ requiredBuckets:
158
+ anyOf:
159
+ - type: integer
160
+ - type: "null"
161
+ description: >-
162
+ How many buckets must breach before the rule fires. `null` means
163
+ every bucket that had data.
36
164
  severity:
37
165
  type: string
38
166
  enum:
@@ -56,9 +184,25 @@ ListAlertRulesResponse:
56
184
  - name
57
185
  - metricId
58
186
  - metricName
187
+ - kind
59
188
  - comparator
60
189
  - threshold
190
+ - rangeMin
191
+ - rangeMax
192
+ - sigma
193
+ - direction
194
+ - sustained
195
+ - warnThreshold
196
+ - recoveryThreshold
197
+ - groupAlerts
198
+ - maxGroups
199
+ - noDataMinutes
200
+ - delayMinutes
201
+ - autoResolveMinutes
202
+ - composite
61
203
  - windowHours
204
+ - bucketMinutes
205
+ - requiredBuckets
62
206
  - severity
63
207
  - enabled
64
208
  - notify
@@ -78,26 +222,189 @@ CreateAlertRuleRequest:
78
222
  type: string
79
223
  minLength: 1
80
224
  description: Id of the metric to evaluate.
225
+ kind:
226
+ default: normal
227
+ description: >-
228
+ What the rule watches for. `normal` compares the metric's own value —
229
+ against `threshold` via `comparator`, or against the
230
+ [`rangeMin`, `rangeMax`] corridor it must stay inside. `anomaly` compares
231
+ it against its own seasonal baseline in sigmas: the same fit a chart
232
+ draws as the band, so a point outside the drawn band is exactly a
233
+ breaching bucket, and one metric can back rules at several sigmas.
234
+ `outliers` compares each `by()` group against its peers in the same
235
+ bucket and is refused on a metric with no grouping.
236
+ type: string
237
+ enum:
238
+ - normal
239
+ - anomaly
240
+ - outliers
241
+ - no_data
242
+ - composite
81
243
  comparator:
244
+ default: gt
245
+ description: How the metric value is compared to the threshold. Read only by a `normal` rule with no range set.
82
246
  type: string
83
247
  enum:
84
248
  - gt
85
249
  - gte
86
250
  - lt
87
251
  - lte
88
- description: How the metric value is compared to the threshold (gt/gte/lt/lte).
89
252
  threshold:
90
253
  type: number
91
- description: Threshold the metric value is compared against.
254
+ description: >-
255
+ Threshold the metric value is compared against. Required for a `normal`
256
+ rule unless a range is given; ignored by every other kind.
257
+ rangeMin:
258
+ anyOf:
259
+ - type: number
260
+ - type: "null"
261
+ description: >-
262
+ Lower edge of the corridor the metric must stay inside. Setting either
263
+ bound switches a `normal` rule off `comparator`/`threshold` and onto the
264
+ range, which breaches on LEAVING it in either direction. One bound alone
265
+ is a one-sided range.
266
+ rangeMax:
267
+ anyOf:
268
+ - type: number
269
+ - type: "null"
270
+ description: Upper edge of the corridor the metric must stay inside.
271
+ sigma:
272
+ anyOf:
273
+ - type: number
274
+ exclusiveMinimum: 0
275
+ - type: "null"
276
+ description: >-
277
+ How far from normal is too far, for `anomaly` and `outliers`, in
278
+ sigma-equivalents. Omit (or `null`) for 3, which is also the half-width
279
+ of the band a chart draws — so an unconfigured rule fires exactly where
280
+ the picture says it would.
281
+ direction:
282
+ anyOf:
283
+ - type: string
284
+ enum:
285
+ - above
286
+ - below
287
+ - either
288
+ - type: "null"
289
+ description: >-
290
+ Which side of the baseline an `anomaly` or `outliers` rule watches. Omit
291
+ (or `null`) for either, which is what "is this unusual" means; name a
292
+ side for a metric that is only bad in one direction.
293
+ sustained:
294
+ default: false
295
+ type: boolean
296
+ description: >-
297
+ Judge the window by ACCUMULATED departure instead of bucket by bucket.
298
+ The ordinary check asks every bucket to be past `sigma`, which a slow
299
+ regression never manages: a metric running 1.5σ worse than normal since
300
+ Tuesday puts no single bucket past 3σ and pages nobody. A sustained rule
301
+ adds up each bucket's excess over half a sigma and fires once the total
302
+ passes twice `sigma` — four buckets at 2σ do it, a lone 3σ spike does
303
+ not. `anomaly` and `outliers` only, and refused alongside
304
+ `requiredBuckets`: the accumulated bar IS the evidence bar, and a second
305
+ one counted in buckets would silently override it.
92
306
  windowHours:
93
307
  default: 24
94
308
  description: Lookback window (hours) the metric is aggregated over. 1–720.
95
309
  type: integer
96
310
  minimum: 1
97
311
  maximum: 720
312
+ bucketMinutes:
313
+ anyOf:
314
+ - type: integer
315
+ minimum: 1
316
+ maximum: 43200
317
+ - type: "null"
318
+ description: >-
319
+ Width of the buckets the window is split into, in minutes. Omit (or
320
+ `null`) to divide the window into 12 equal buckets. The rule is judged
321
+ per bucket, so this is the resolution at which "sustained" is measured —
322
+ a short bucket asks the condition to hold through finer detail.
323
+ requiredBuckets:
324
+ anyOf:
325
+ - type: integer
326
+ minimum: 1
327
+ - type: "null"
328
+ description: >-
329
+ How many buckets must breach before the rule fires. Omit (or `null`) to
330
+ require every bucket that had data. Buckets with nothing in them are
331
+ excluded before this is counted, so on a sparse metric `null` can mean a
332
+ single bucket — set this to 2 or more where one lone sample must never
333
+ page.
334
+ warnThreshold:
335
+ anyOf:
336
+ - type: number
337
+ - type: "null"
338
+ description: >-
339
+ The milder bound — a second level, in the same unit the kind judges in,
340
+ that raises a quieter alert before the firing level is reached. Must be
341
+ strictly milder than the level the rule fires at, or it could never fire
342
+ on its own. Refused on a range rule and on a `sustained` one: neither
343
+ condition is a single number, so there is nothing to substitute.
344
+ recoveryThreshold:
345
+ anyOf:
346
+ - type: number
347
+ - type: "null"
348
+ description: >-
349
+ What the metric must get back to before a live alert closes. Without it a
350
+ metric sitting on its threshold pages and clears once per tick. Must be
351
+ on the safe side of the firing level, or equal to it. Omit (or `null`)
352
+ for no hysteresis.
353
+ groupAlerts:
354
+ default: false
355
+ type: boolean
356
+ description: >-
357
+ Fire one alert per `by()` group instead of one for the whole metric. The
358
+ firing key becomes (rule, group), so each group raises, dedupes and
359
+ recovers on its own. Refused on a metric with no `by()`, and on `no_data`
360
+ — a group that went silent has no rows left to be missing from.
361
+ maxGroups:
362
+ anyOf:
363
+ - type: integer
364
+ minimum: 1
365
+ maximum: 50
366
+ - type: "null"
367
+ description: >-
368
+ How many groups this rule may alert on at once. Omit (or `null`) for 10.
369
+ Past the cap the worst groups fire and the count of the rest is stated on
370
+ each ticket — a group set is customer data, and an uncapped rule on
371
+ `by(user_id)` would file a ticket per user.
372
+ noDataMinutes:
373
+ anyOf:
374
+ - type: integer
375
+ minimum: 5
376
+ maximum: 43200
377
+ - type: "null"
378
+ description: >-
379
+ For a `no_data` rule: how long the silence must last. Omit (or `null`)
380
+ for 15 minutes. Five is the floor — below it, ordinary ingest lag empties
381
+ the trailing bucket and reads as an outage.
382
+ delayMinutes:
383
+ anyOf:
384
+ - type: integer
385
+ minimum: 1
386
+ maximum: 1440
387
+ - type: "null"
388
+ description: >-
389
+ Hold the evaluated window back this far behind live, on top of the
390
+ reader's own settle grace. For a metric assembled from a source that
391
+ lands in batches, whose trailing buckets are legitimately incomplete for
392
+ longer than the grace covers.
393
+ autoResolveMinutes:
394
+ anyOf:
395
+ - type: integer
396
+ minimum: 1
397
+ - type: "null"
398
+ description: >-
399
+ Close a live instance that has gone this long without a fresh verdict. A
400
+ rule that cannot reach a verdict deliberately leaves its alert alone,
401
+ which is right for a tick and wrong for a week. Never closes an instance
402
+ that is currently breaching. Omit (or `null`) to never auto-resolve.
403
+ composite:
404
+ $ref: "#/AlertCompositeSpec"
98
405
  severity:
99
406
  default: warn
100
- description: Severity of the raised alert.
407
+ description: Severity of the raised alert. A `warnThreshold` breach opens one step quieter than this.
101
408
  type: string
102
409
  enum:
103
410
  - danger
@@ -112,8 +419,6 @@ CreateAlertRuleRequest:
112
419
  required:
113
420
  - name
114
421
  - metricId
115
- - comparator
116
- - threshold
117
422
  additionalProperties: false
118
423
  CreateAlertRuleResponse:
119
424
  type: object
@@ -131,6 +436,14 @@ UpdateAlertRuleRequest:
131
436
  type: string
132
437
  minLength: 1
133
438
  maxLength: 120
439
+ kind:
440
+ type: string
441
+ enum:
442
+ - normal
443
+ - anomaly
444
+ - outliers
445
+ - no_data
446
+ - composite
134
447
  comparator:
135
448
  type: string
136
449
  enum:
@@ -140,10 +453,95 @@ UpdateAlertRuleRequest:
140
453
  - lte
141
454
  threshold:
142
455
  type: number
456
+ rangeMin:
457
+ anyOf:
458
+ - type: number
459
+ - type: "null"
460
+ description: Lower edge of the corridor; `null` drops it, returning the rule to comparator and threshold when both bounds are gone.
461
+ rangeMax:
462
+ anyOf:
463
+ - type: number
464
+ - type: "null"
465
+ description: Upper edge of the corridor; `null` drops it.
466
+ sigma:
467
+ anyOf:
468
+ - type: number
469
+ exclusiveMinimum: 0
470
+ - type: "null"
471
+ description: Sigmas an `anomaly` or `outliers` rule fires at; `null` restores the default of 3.
472
+ direction:
473
+ anyOf:
474
+ - type: string
475
+ enum:
476
+ - above
477
+ - below
478
+ - either
479
+ - type: "null"
480
+ description: Which side an `anomaly` or `outliers` rule watches; `null` restores either.
481
+ sustained:
482
+ type: boolean
483
+ description: >-
484
+ Judge the window by accumulated departure rather than bucket by bucket.
485
+ `anomaly` and `outliers` only; refused alongside `requiredBuckets`.
486
+ warnThreshold:
487
+ anyOf:
488
+ - type: number
489
+ - type: "null"
490
+ description: The milder bound; `null` drops the warning level, leaving the rule with one.
491
+ recoveryThreshold:
492
+ anyOf:
493
+ - type: number
494
+ - type: "null"
495
+ description: What the metric must get back to before a live alert closes; `null` drops the hysteresis.
496
+ groupAlerts:
497
+ type: boolean
498
+ description: Fire one alert per `by()` group. Refused on a metric with no grouping.
499
+ maxGroups:
500
+ anyOf:
501
+ - type: integer
502
+ minimum: 1
503
+ maximum: 50
504
+ - type: "null"
505
+ description: How many groups may alert at once; `null` restores the default of 10.
506
+ noDataMinutes:
507
+ anyOf:
508
+ - type: integer
509
+ minimum: 5
510
+ maximum: 43200
511
+ - type: "null"
512
+ description: How long a `no_data` rule's silence must last; `null` restores 15 minutes.
513
+ delayMinutes:
514
+ anyOf:
515
+ - type: integer
516
+ minimum: 1
517
+ maximum: 1440
518
+ - type: "null"
519
+ description: How far behind live the window is held; `null` drops the delay.
520
+ autoResolveMinutes:
521
+ anyOf:
522
+ - type: integer
523
+ minimum: 1
524
+ - type: "null"
525
+ description: Close a stale live instance after this long; `null` never auto-resolves.
526
+ composite:
527
+ $ref: "#/AlertCompositeSpec"
143
528
  windowHours:
144
529
  type: integer
145
530
  minimum: 1
146
531
  maximum: 720
532
+ bucketMinutes:
533
+ anyOf:
534
+ - type: integer
535
+ minimum: 1
536
+ maximum: 43200
537
+ - type: "null"
538
+ description: Bucket width in minutes; `null` restores the default 12 buckets per window.
539
+ requiredBuckets:
540
+ anyOf:
541
+ - type: integer
542
+ minimum: 1
543
+ - type: "null"
544
+ description: Buckets that must breach to fire; `null` restores "every bucket that had data".
147
545
  severity:
148
546
  type: string
149
547
  enum:
@@ -1,13 +1,15 @@
1
1
  AlertApiRow:
2
2
  type: object
3
3
  description: |-
4
- One FIRED alert — a condition the platform observed and raised, not the rule that defines it (alert *rules* are the `/api/admin/alert-rules` resource). Alerts are never filed by hand: the UI (killswitch flips) and the worker (analysis consumer + alerts cron) raise them, keyed by `(source, dedupeKey)` so re-raising refreshes the active row and a clearing condition auto-resolves it.
4
+ One FIRED alert — an INSTANCE of a condition the platform observed, not the rule that defines it (alert *rules* are the `/api/admin/alert-rules` resource). Instances are never filed by hand: the UI (killswitch flips) and the worker (analysis consumer + alerts cron) open them, keyed by `(source, dedupeKey)`, and at most one instance per condition is open at a time — re-raising refreshes the open one, and the condition clearing closes it.
5
+
6
+ An instance IS an ops queue item (`/api/admin/ops` with `type: "alert"`) seen through the alert lens: same `id`, `severity` is that item's `priority`, and `status` is its work status projected onto three states. Resolving the queue item and clearing the alert are therefore the same act.
5
7
 
6
8
  Every field is always present on the wire; nullable fields are `null`, never absent.
7
9
  properties:
8
10
  id:
9
11
  type: string
10
- description: Stable opaque alert id.
12
+ description: Stable opaque instance id — also the id of the ops queue item this instance is.
11
13
  source:
12
14
  type: string
13
15
  enum:
@@ -30,10 +32,10 @@ AlertApiRow:
30
32
  - danger
31
33
  - warn
32
34
  - info
33
- description: How loud the alert is — `danger` pages, `warn` is the default, `info` is FYI-only.
35
+ description: How loud the alert is — `danger` pages, `warn` is the default, `info` is FYI-only. Severity belongs to the RULE and seeds the instance's queue `priority` (`danger`→`critical`, `warn`→`high`, `info`→`medium`); it is read back from there, so re-prioritising the queue item changes what this reports.
34
36
  dedupeKey:
35
37
  type: string
36
- description: Idempotency + reconcile key within `(project, source)` — re-raising the same condition refreshes the existing active row instead of stacking duplicates, and the condition clearing flips that row to `resolved`.
38
+ description: Idempotency + reconcile key within `(project, source)` — re-raising the same condition refreshes the open instance instead of stacking duplicates, and the condition clearing closes it. A later firing of the same condition opens a NEW instance with its own `id`.
37
39
  title:
38
40
  type: string
39
41
  description: Short human-readable headline (what fired).
@@ -56,10 +58,10 @@ AlertApiRow:
56
58
  - active
57
59
  - resolved
58
60
  - dismissed
59
- description: Lifecycle state. `active` means the condition currently holds (shown as "Firing" in the UI); `resolved` means it cleared (auto, when the raiser reconciles, or by hand); `dismissed` means a human waved it off.
61
+ description: 'Lifecycle state, projected from the queue item''s work status. `active` means the item is still open, i.e. the condition holds (shown as "Firing" in the UI); `resolved` means it cleared (auto, when the raiser reconciles, or by hand); `dismissed` means a human waved it off (the item is closed `wont_fix`).'
60
62
  createdAt:
61
63
  type: string
62
- description: ISO-8601 timestamp the alert was first raised.
64
+ description: ISO-8601 timestamp this instance opened — when the condition started firing.
63
65
  resolvedAt:
64
66
  description: ISO-8601 timestamp the alert flipped to `resolved`, or `null` while it hasn't.
65
67
  anyOf:
@@ -121,7 +123,7 @@ UpdateAlertRequest:
121
123
  - active
122
124
  - resolved
123
125
  - dismissed
124
- description: New lifecycle state. `resolved` / `dismissed` stamp their timestamp; `active` re-opens and clears both.
126
+ description: New lifecycle state, written through to the queue item (`resolved` / `wont_fix` / `open`). `resolved` / `dismissed` stamp their timestamp; `active` re-opens and clears both.
125
127
  assigneeId:
126
128
  description: PERSON owner — a `users.id`, or `null` to clear the assignment.
127
129
  anyOf:
@@ -95,8 +95,8 @@ ExperimentInlineMetric:
95
95
  event: { type: string, minLength: 1, maxLength: 256, description: Event name to build the metric from server-side. Auto-created if missing. }
96
96
  aggregation:
97
97
  type: string
98
- enum: [count_users, count_events, retention_7d, retention_30d, sum, avg]
99
- description: Reducer for the `event` form. Defaults to `count_users`.
98
+ enum: [count_events, sum, avg]
99
+ description: "Reducer for the `event` form. Defaults to `count_events`. `count_users` was removed: Analytics Engine samples rows and weights the survivors, and a distinct count cannot be reweighted, so it under-reports by the sample interval. `retention_7d` / `retention_30d` were removed on 2026-08-10: the day count reached no query and no reducer, so both produced the same number as a distinct user count and reported it as a returning-user rate."
100
100
  value: { type: string, minLength: 1, maxLength: 256, description: Numeric event property for `sum`/`avg` (with `event`). }
101
101
  min_effect_of_interest:
102
102
  description: Per-experiment override of the metric's default minimum effect of interest (relative, 0–1) — the smallest change worth acting on for this experiment's decision. `null`/omitted inherits the metric default. For a guardrail, the non-inferiority margin.