@shipeasy/openapi 3.2.0 → 3.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.
Files changed (40) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-RTN572TI.js → chunk-JJA7XEPU.js} +91 -1
  3. package/dist/chunk-JJA7XEPU.js.map +1 -0
  4. package/dist/{chunk-DW33MG4B.js → chunk-Z3CY5Y44.js} +514 -126
  5. package/dist/chunk-Z3CY5Y44.js.map +1 -0
  6. package/dist/{client-BPVn4hMC.d.ts → client-CkRbr8I2.d.ts} +1544 -212
  7. package/dist/client.d.ts +1 -1
  8. package/dist/client.js +23 -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 +24 -2
  14. package/dist/index.js.map +1 -1
  15. package/dist/schemas.d.ts +1935 -963
  16. package/dist/schemas.js +141 -1
  17. package/openapi-sdk.json +31 -9
  18. package/openapi.json +10449 -8118
  19. package/openapi.yaml +2274 -634
  20. package/package.json +1 -1
  21. package/spec/components/schemas/alert-rules.yaml +434 -10
  22. package/spec/components/schemas/alerts.yaml +9 -7
  23. package/spec/components/schemas/common.yaml +2 -2
  24. package/spec/components/schemas/connectors.yaml +69 -6
  25. package/spec/components/schemas/events.yaml +20 -9
  26. package/spec/components/schemas/keys.yaml +39 -0
  27. package/spec/components/schemas/metrics.yaml +131 -251
  28. package/spec/components/schemas/ops.yaml +471 -29
  29. package/spec/components/schemas/projects.yaml +18 -0
  30. package/spec/components/schemas/search.yaml +1 -0
  31. package/spec/components/schemas/tags.yaml +91 -0
  32. package/spec/openapi.yaml +56 -0
  33. package/spec/paths/alerts.yaml +5 -3
  34. package/spec/paths/i18n.yaml +2 -1
  35. package/spec/paths/keys.yaml +79 -0
  36. package/spec/paths/metrics.yaml +26 -17
  37. package/spec/paths/ops.yaml +514 -11
  38. package/spec/paths/tags.yaml +270 -0
  39. package/dist/chunk-DW33MG4B.js.map +0 -1
  40. package/dist/chunk-RTN572TI.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipeasy/openapi",
3
- "version": "3.2.0",
3
+ "version": "3.4.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",
@@ -1,3 +1,56 @@
1
+ AlertRuleNotify:
2
+ anyOf:
3
+ - type: object
4
+ properties:
5
+ slackChannel:
6
+ anyOf:
7
+ - type: object
8
+ properties:
9
+ id: { type: string, minLength: 1 }
10
+ name: { type: string, minLength: 1 }
11
+ required: [id, name]
12
+ additionalProperties: false
13
+ - type: "null"
14
+ email:
15
+ anyOf:
16
+ - type: string
17
+ format: email
18
+ - type: "null"
19
+ inApp:
20
+ anyOf:
21
+ - type: boolean
22
+ - type: "null"
23
+ description: Whether the in-app notification (the dashboard bell and Home) is recorded when the rule fires. Absent or `null` means on; `false` skips it for this rule.
24
+ additionalProperties: false
25
+ - type: "null"
26
+ description: Delivery target for a rule's alert; `null` = use the project default. The `NotificationTarget` shape plus the in-app switch.
27
+ AlertCompositeSpec:
28
+ type: object
29
+ description: >-
30
+ What a `composite` rule is a boolean over. `rules` are sibling alert-rule ids
31
+ in the same project and `op` is how their states combine. A child's state is
32
+ what the current pass concluded about it, falling back to whether it has an
33
+ open instance — so a composite still means something on a tick where a child
34
+ could not be evaluated, which is exactly the tick a "two of these are broken
35
+ at once" rule is for. One level deep: a child may not itself be composite.
36
+ properties:
37
+ op:
38
+ type: string
39
+ enum:
40
+ - and
41
+ - or
42
+ description: How the children's states combine.
43
+ rules:
44
+ type: array
45
+ minItems: 2
46
+ items:
47
+ type: string
48
+ minLength: 1
49
+ description: Ids of the alert rules to combine. Stored sorted and de-duplicated.
50
+ required:
51
+ - op
52
+ - rules
53
+ additionalProperties: false
1
54
  ListAlertRulesResponse:
2
55
  type: array
3
56
  items:
@@ -17,6 +70,26 @@ ListAlertRulesResponse:
17
70
  - type: string
18
71
  - type: "null"
19
72
  description: Display name of the bound metric, or `null` if the metric no longer exists.
73
+ kind:
74
+ type: string
75
+ enum:
76
+ - normal
77
+ - anomaly
78
+ - outliers
79
+ - no_data
80
+ - composite
81
+ description: >-
82
+ What the rule watches for. `normal` compares the metric's own value —
83
+ against `threshold` via `comparator`, or against the
84
+ [`rangeMin`, `rangeMax`] corridor it must stay inside. `anomaly`
85
+ compares it against ITS OWN seasonal baseline in sigmas, which is the
86
+ same fit a chart draws as the band. `outliers` compares each `by()`
87
+ group against its peers in the same bucket, so it needs a grouped
88
+ metric. `no_data` fires when the metric records nothing at all for
89
+ `noDataMinutes` — a count of zero is not silence, and the two are
90
+ distinguished by the reader's presence columns. `composite` is a
91
+ boolean over OTHER rules' current state and watches no metric of its
92
+ own.
20
93
  comparator:
21
94
  type: string
22
95
  enum:
@@ -24,15 +97,96 @@ ListAlertRulesResponse:
24
97
  - gte
25
98
  - lt
26
99
  - lte
27
- description: How the metric value is compared to the threshold (gt/gte/lt/lte).
100
+ 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
101
  threshold:
29
102
  type: number
30
- description: Threshold the metric value is compared against.
103
+ description: Threshold the metric value is compared against. Read only by a `normal` rule with no range set.
104
+ rangeMin:
105
+ anyOf:
106
+ - type: number
107
+ - type: "null"
108
+ description: Lower edge of the corridor a `normal` rule must stay inside; `null` if the rule is judged by comparator and threshold.
109
+ rangeMax:
110
+ anyOf:
111
+ - type: number
112
+ - type: "null"
113
+ description: Upper edge of the corridor a `normal` rule must stay inside; `null` if the rule is judged by comparator and threshold.
114
+ sigma:
115
+ anyOf:
116
+ - type: number
117
+ - type: "null"
118
+ 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.
119
+ direction:
120
+ anyOf:
121
+ - type: string
122
+ enum:
123
+ - above
124
+ - below
125
+ - either
126
+ - type: "null"
127
+ description: Which side of the baseline an `anomaly` or `outliers` rule watches. `null` means either.
128
+ sustained:
129
+ type: boolean
130
+ description: >-
131
+ Whether the rule is judged by ACCUMULATED departure rather than bucket
132
+ by bucket. `anomaly` and `outliers` only.
133
+ warnThreshold:
134
+ anyOf:
135
+ - type: number
136
+ - type: "null"
137
+ description: The milder bound, in the same unit the kind judges in. `null` means the rule has one level.
138
+ recoveryThreshold:
139
+ anyOf:
140
+ - type: number
141
+ - type: "null"
142
+ description: What the metric must get back to before a live alert closes. `null` means no hysteresis — the firing bound clears it.
143
+ groupAlerts:
144
+ type: boolean
145
+ description: Whether the rule fires one alert per `by()` group rather than one for the whole metric.
146
+ maxGroups:
147
+ anyOf:
148
+ - type: integer
149
+ - type: "null"
150
+ description: How many groups this rule may alert on at once. `null` takes the shipped cap of 10.
151
+ noDataMinutes:
152
+ anyOf:
153
+ - type: integer
154
+ - type: "null"
155
+ description: How long the silence must last for a `no_data` rule. `null` on every other kind.
156
+ delayMinutes:
157
+ anyOf:
158
+ - type: integer
159
+ - type: "null"
160
+ description: How far behind live the evaluated window is held, on top of the reader's settle grace. `null` means no extra delay.
161
+ autoResolveMinutes:
162
+ anyOf:
163
+ - type: integer
164
+ - type: "null"
165
+ description: Close a live instance that has gone this long without a fresh verdict. `null` never auto-resolves.
166
+ composite:
167
+ anyOf:
168
+ - $ref: "#/AlertCompositeSpec"
169
+ - type: "null"
170
+ description: For a `composite` rule, the child rules and how their states combine. `null` on every other kind.
31
171
  windowHours:
32
172
  type: integer
33
173
  minimum: -9007199254740991
34
174
  maximum: 9007199254740991
35
175
  description: Lookback window (hours) the metric is aggregated over.
176
+ bucketMinutes:
177
+ anyOf:
178
+ - type: integer
179
+ - type: "null"
180
+ description: >-
181
+ Width of the buckets the window is split into, in minutes. `null`
182
+ divides the window into 12 equal buckets.
183
+ requiredBuckets:
184
+ anyOf:
185
+ - type: integer
186
+ - type: "null"
187
+ description: >-
188
+ How many buckets must breach before the rule fires. `null` means
189
+ every bucket that had data.
36
190
  severity:
37
191
  type: string
38
192
  enum:
@@ -44,7 +198,7 @@ ListAlertRulesResponse:
44
198
  type: boolean
45
199
  description: Whether the rule is evaluated by the cron.
46
200
  notify:
47
- $ref: ./common.yaml#/NotificationTarget
201
+ $ref: "#/AlertRuleNotify"
48
202
  createdAt:
49
203
  type: string
50
204
  description: ISO-8601 timestamp of creation.
@@ -56,9 +210,25 @@ ListAlertRulesResponse:
56
210
  - name
57
211
  - metricId
58
212
  - metricName
213
+ - kind
59
214
  - comparator
60
215
  - threshold
216
+ - rangeMin
217
+ - rangeMax
218
+ - sigma
219
+ - direction
220
+ - sustained
221
+ - warnThreshold
222
+ - recoveryThreshold
223
+ - groupAlerts
224
+ - maxGroups
225
+ - noDataMinutes
226
+ - delayMinutes
227
+ - autoResolveMinutes
228
+ - composite
61
229
  - windowHours
230
+ - bucketMinutes
231
+ - requiredBuckets
62
232
  - severity
63
233
  - enabled
64
234
  - notify
@@ -78,26 +248,189 @@ CreateAlertRuleRequest:
78
248
  type: string
79
249
  minLength: 1
80
250
  description: Id of the metric to evaluate.
251
+ kind:
252
+ default: normal
253
+ description: >-
254
+ What the rule watches for. `normal` compares the metric's own value —
255
+ against `threshold` via `comparator`, or against the
256
+ [`rangeMin`, `rangeMax`] corridor it must stay inside. `anomaly` compares
257
+ it against its own seasonal baseline in sigmas: the same fit a chart
258
+ draws as the band, so a point outside the drawn band is exactly a
259
+ breaching bucket, and one metric can back rules at several sigmas.
260
+ `outliers` compares each `by()` group against its peers in the same
261
+ bucket and is refused on a metric with no grouping.
262
+ type: string
263
+ enum:
264
+ - normal
265
+ - anomaly
266
+ - outliers
267
+ - no_data
268
+ - composite
81
269
  comparator:
270
+ default: gt
271
+ description: How the metric value is compared to the threshold. Read only by a `normal` rule with no range set.
82
272
  type: string
83
273
  enum:
84
274
  - gt
85
275
  - gte
86
276
  - lt
87
277
  - lte
88
- description: How the metric value is compared to the threshold (gt/gte/lt/lte).
89
278
  threshold:
90
279
  type: number
91
- description: Threshold the metric value is compared against.
280
+ description: >-
281
+ Threshold the metric value is compared against. Required for a `normal`
282
+ rule unless a range is given; ignored by every other kind.
283
+ rangeMin:
284
+ anyOf:
285
+ - type: number
286
+ - type: "null"
287
+ description: >-
288
+ Lower edge of the corridor the metric must stay inside. Setting either
289
+ bound switches a `normal` rule off `comparator`/`threshold` and onto the
290
+ range, which breaches on LEAVING it in either direction. One bound alone
291
+ is a one-sided range.
292
+ rangeMax:
293
+ anyOf:
294
+ - type: number
295
+ - type: "null"
296
+ description: Upper edge of the corridor the metric must stay inside.
297
+ sigma:
298
+ anyOf:
299
+ - type: number
300
+ exclusiveMinimum: 0
301
+ - type: "null"
302
+ description: >-
303
+ How far from normal is too far, for `anomaly` and `outliers`, in
304
+ sigma-equivalents. Omit (or `null`) for 3, which is also the half-width
305
+ of the band a chart draws — so an unconfigured rule fires exactly where
306
+ the picture says it would.
307
+ direction:
308
+ anyOf:
309
+ - type: string
310
+ enum:
311
+ - above
312
+ - below
313
+ - either
314
+ - type: "null"
315
+ description: >-
316
+ Which side of the baseline an `anomaly` or `outliers` rule watches. Omit
317
+ (or `null`) for either, which is what "is this unusual" means; name a
318
+ side for a metric that is only bad in one direction.
319
+ sustained:
320
+ default: false
321
+ type: boolean
322
+ description: >-
323
+ Judge the window by ACCUMULATED departure instead of bucket by bucket.
324
+ The ordinary check asks every bucket to be past `sigma`, which a slow
325
+ regression never manages: a metric running 1.5σ worse than normal since
326
+ Tuesday puts no single bucket past 3σ and pages nobody. A sustained rule
327
+ adds up each bucket's excess over half a sigma and fires once the total
328
+ passes twice `sigma` — four buckets at 2σ do it, a lone 3σ spike does
329
+ not. `anomaly` and `outliers` only, and refused alongside
330
+ `requiredBuckets`: the accumulated bar IS the evidence bar, and a second
331
+ one counted in buckets would silently override it.
92
332
  windowHours:
93
333
  default: 24
94
334
  description: Lookback window (hours) the metric is aggregated over. 1–720.
95
335
  type: integer
96
336
  minimum: 1
97
337
  maximum: 720
338
+ bucketMinutes:
339
+ anyOf:
340
+ - type: integer
341
+ minimum: 1
342
+ maximum: 43200
343
+ - type: "null"
344
+ description: >-
345
+ Width of the buckets the window is split into, in minutes. Omit (or
346
+ `null`) to divide the window into 12 equal buckets. The rule is judged
347
+ per bucket, so this is the resolution at which "sustained" is measured —
348
+ a short bucket asks the condition to hold through finer detail.
349
+ requiredBuckets:
350
+ anyOf:
351
+ - type: integer
352
+ minimum: 1
353
+ - type: "null"
354
+ description: >-
355
+ How many buckets must breach before the rule fires. Omit (or `null`) to
356
+ require every bucket that had data. Buckets with nothing in them are
357
+ excluded before this is counted, so on a sparse metric `null` can mean a
358
+ single bucket — set this to 2 or more where one lone sample must never
359
+ page.
360
+ warnThreshold:
361
+ anyOf:
362
+ - type: number
363
+ - type: "null"
364
+ description: >-
365
+ The milder bound — a second level, in the same unit the kind judges in,
366
+ that raises a quieter alert before the firing level is reached. Must be
367
+ strictly milder than the level the rule fires at, or it could never fire
368
+ on its own. Refused on a range rule and on a `sustained` one: neither
369
+ condition is a single number, so there is nothing to substitute.
370
+ recoveryThreshold:
371
+ anyOf:
372
+ - type: number
373
+ - type: "null"
374
+ description: >-
375
+ What the metric must get back to before a live alert closes. Without it a
376
+ metric sitting on its threshold pages and clears once per tick. Must be
377
+ on the safe side of the firing level, or equal to it. Omit (or `null`)
378
+ for no hysteresis.
379
+ groupAlerts:
380
+ default: false
381
+ type: boolean
382
+ description: >-
383
+ Fire one alert per `by()` group instead of one for the whole metric. The
384
+ firing key becomes (rule, group), so each group raises, dedupes and
385
+ recovers on its own. Refused on a metric with no `by()`, and on `no_data`
386
+ — a group that went silent has no rows left to be missing from.
387
+ maxGroups:
388
+ anyOf:
389
+ - type: integer
390
+ minimum: 1
391
+ maximum: 50
392
+ - type: "null"
393
+ description: >-
394
+ How many groups this rule may alert on at once. Omit (or `null`) for 10.
395
+ Past the cap the worst groups fire and the count of the rest is stated on
396
+ each ticket — a group set is customer data, and an uncapped rule on
397
+ `by(user_id)` would file a ticket per user.
398
+ noDataMinutes:
399
+ anyOf:
400
+ - type: integer
401
+ minimum: 5
402
+ maximum: 43200
403
+ - type: "null"
404
+ description: >-
405
+ For a `no_data` rule: how long the silence must last. Omit (or `null`)
406
+ for 15 minutes. Five is the floor — below it, ordinary ingest lag empties
407
+ the trailing bucket and reads as an outage.
408
+ delayMinutes:
409
+ anyOf:
410
+ - type: integer
411
+ minimum: 1
412
+ maximum: 1440
413
+ - type: "null"
414
+ description: >-
415
+ Hold the evaluated window back this far behind live, on top of the
416
+ reader's own settle grace. For a metric assembled from a source that
417
+ lands in batches, whose trailing buckets are legitimately incomplete for
418
+ longer than the grace covers.
419
+ autoResolveMinutes:
420
+ anyOf:
421
+ - type: integer
422
+ minimum: 1
423
+ - type: "null"
424
+ description: >-
425
+ Close a live instance that has gone this long without a fresh verdict. A
426
+ rule that cannot reach a verdict deliberately leaves its alert alone,
427
+ which is right for a tick and wrong for a week. Never closes an instance
428
+ that is currently breaching. Omit (or `null`) to never auto-resolve.
429
+ composite:
430
+ $ref: "#/AlertCompositeSpec"
98
431
  severity:
99
432
  default: warn
100
- description: Severity of the raised alert.
433
+ description: Severity of the raised alert. A `warnThreshold` breach opens one step quieter than this.
101
434
  type: string
102
435
  enum:
103
436
  - danger
@@ -108,12 +441,10 @@ CreateAlertRuleRequest:
108
441
  description: Whether the rule is evaluated by the cron.
109
442
  type: boolean
110
443
  notify:
111
- $ref: ./common.yaml#/NotificationTarget
444
+ $ref: "#/AlertRuleNotify"
112
445
  required:
113
446
  - name
114
447
  - metricId
115
- - comparator
116
- - threshold
117
448
  additionalProperties: false
118
449
  CreateAlertRuleResponse:
119
450
  type: object
@@ -131,6 +462,14 @@ UpdateAlertRuleRequest:
131
462
  type: string
132
463
  minLength: 1
133
464
  maxLength: 120
465
+ kind:
466
+ type: string
467
+ enum:
468
+ - normal
469
+ - anomaly
470
+ - outliers
471
+ - no_data
472
+ - composite
134
473
  comparator:
135
474
  type: string
136
475
  enum:
@@ -140,10 +479,95 @@ UpdateAlertRuleRequest:
140
479
  - lte
141
480
  threshold:
142
481
  type: number
482
+ rangeMin:
483
+ anyOf:
484
+ - type: number
485
+ - type: "null"
486
+ description: Lower edge of the corridor; `null` drops it, returning the rule to comparator and threshold when both bounds are gone.
487
+ rangeMax:
488
+ anyOf:
489
+ - type: number
490
+ - type: "null"
491
+ description: Upper edge of the corridor; `null` drops it.
492
+ sigma:
493
+ anyOf:
494
+ - type: number
495
+ exclusiveMinimum: 0
496
+ - type: "null"
497
+ description: Sigmas an `anomaly` or `outliers` rule fires at; `null` restores the default of 3.
498
+ direction:
499
+ anyOf:
500
+ - type: string
501
+ enum:
502
+ - above
503
+ - below
504
+ - either
505
+ - type: "null"
506
+ description: Which side an `anomaly` or `outliers` rule watches; `null` restores either.
507
+ sustained:
508
+ type: boolean
509
+ description: >-
510
+ Judge the window by accumulated departure rather than bucket by bucket.
511
+ `anomaly` and `outliers` only; refused alongside `requiredBuckets`.
512
+ warnThreshold:
513
+ anyOf:
514
+ - type: number
515
+ - type: "null"
516
+ description: The milder bound; `null` drops the warning level, leaving the rule with one.
517
+ recoveryThreshold:
518
+ anyOf:
519
+ - type: number
520
+ - type: "null"
521
+ description: What the metric must get back to before a live alert closes; `null` drops the hysteresis.
522
+ groupAlerts:
523
+ type: boolean
524
+ description: Fire one alert per `by()` group. Refused on a metric with no grouping.
525
+ maxGroups:
526
+ anyOf:
527
+ - type: integer
528
+ minimum: 1
529
+ maximum: 50
530
+ - type: "null"
531
+ description: How many groups may alert at once; `null` restores the default of 10.
532
+ noDataMinutes:
533
+ anyOf:
534
+ - type: integer
535
+ minimum: 5
536
+ maximum: 43200
537
+ - type: "null"
538
+ description: How long a `no_data` rule's silence must last; `null` restores 15 minutes.
539
+ delayMinutes:
540
+ anyOf:
541
+ - type: integer
542
+ minimum: 1
543
+ maximum: 1440
544
+ - type: "null"
545
+ description: How far behind live the window is held; `null` drops the delay.
546
+ autoResolveMinutes:
547
+ anyOf:
548
+ - type: integer
549
+ minimum: 1
550
+ - type: "null"
551
+ description: Close a stale live instance after this long; `null` never auto-resolves.
552
+ composite:
553
+ $ref: "#/AlertCompositeSpec"
143
554
  windowHours:
144
555
  type: integer
145
556
  minimum: 1
146
557
  maximum: 720
558
+ bucketMinutes:
559
+ anyOf:
560
+ - type: integer
561
+ minimum: 1
562
+ maximum: 43200
563
+ - type: "null"
564
+ description: Bucket width in minutes; `null` restores the default 12 buckets per window.
565
+ requiredBuckets:
566
+ anyOf:
567
+ - type: integer
568
+ minimum: 1
569
+ - type: "null"
570
+ description: Buckets that must breach to fire; `null` restores "every bucket that had data".
147
571
  severity:
148
572
  type: string
149
573
  enum:
@@ -153,7 +577,7 @@ UpdateAlertRuleRequest:
153
577
  enabled:
154
578
  type: boolean
155
579
  notify:
156
- $ref: ./common.yaml#/NotificationTarget
580
+ $ref: "#/AlertRuleNotify"
157
581
  additionalProperties: false
158
582
  UpdateAlertRuleResponse:
159
583
  type: object
@@ -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.