@crvouga/atlas 1.7.0 → 1.8.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 (31) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/dist/reports/src/explorer-types.d.ts +19 -0
  3. package/dist/reports/src/latency.d.ts +204 -0
  4. package/dist/reports/src/latency.js +219 -0
  5. package/dist/reports/src/reports-explorer-route.d.ts +19 -6
  6. package/dist/reports/src/reports-route.d.ts +19 -6
  7. package/dist/reports/src/test-catalog.d.ts +3 -1
  8. package/dist/reports/src/test-catalog.js +4 -2
  9. package/dist/schema/src/api.d.ts +151 -0
  10. package/dist/schema/src/api.js +4 -3
  11. package/dist/schema/src/layer.d.ts +90 -0
  12. package/dist/schema/src/layer.js +20 -1
  13. package/docs/extending.md +1 -1
  14. package/docs/reference/reports.md +347 -0
  15. package/docs/reference/schema.md +28 -0
  16. package/docs/rich-evidence.md +43 -0
  17. package/docs/ui.md +10 -1
  18. package/llms-full.txt +54 -2
  19. package/package.json +1 -1
  20. package/packages/schema/schemas/check-detail.schema.json +56 -1
  21. package/packages/schema/schemas/effective-delta.schema.json +60 -2
  22. package/packages/schema/schemas/effective-evidence.schema.json +60 -2
  23. package/packages/schema/schemas/layer-rows.schema.json +60 -2
  24. package/packages/schema/schemas/layer.schema.json +112 -2
  25. package/packages/schema/schemas/publish-checks.schema.json +56 -1
  26. package/packages/schema/schemas/test-catalog.schema.json +85 -0
  27. package/packages/schema/schemas/test-history.schema.json +170 -0
  28. package/packages/server/openapi.json +547 -8
  29. package/packages/visualizer/src/evidence/check-detail.tsx +9 -0
  30. package/packages/visualizer/src/explorer/ProductLatency.tsx +214 -0
  31. package/packages/visualizer/src/explorer/TestDetail.tsx +9 -2
@@ -252,6 +252,17 @@ export declare const LayerRowSchema: z.ZodObject<{
252
252
  value: z.ZodNumber;
253
253
  unit: z.ZodString;
254
254
  limit: z.ZodOptional<z.ZodNumber>;
255
+ kind: z.ZodOptional<z.ZodString>;
256
+ from: z.ZodOptional<z.ZodString>;
257
+ to: z.ZodOptional<z.ZodString>;
258
+ intended: z.ZodOptional<z.ZodObject<{
259
+ from: z.ZodOptional<z.ZodString>;
260
+ to: z.ZodOptional<z.ZodString>;
261
+ }, z.core.$loose>>;
262
+ source: z.ZodOptional<z.ZodString>;
263
+ outcome: z.ZodOptional<z.ZodString>;
264
+ instrumentation: z.ZodOptional<z.ZodString>;
265
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
255
266
  }, z.core.$loose>>;
256
267
  testId: z.ZodNullable<z.ZodString>;
257
268
  variant: z.ZodRecord<z.ZodString, z.ZodString>;
@@ -320,6 +331,17 @@ export declare const LayerRowsSchema: z.ZodObject<{
320
331
  value: z.ZodNumber;
321
332
  unit: z.ZodString;
322
333
  limit: z.ZodOptional<z.ZodNumber>;
334
+ kind: z.ZodOptional<z.ZodString>;
335
+ from: z.ZodOptional<z.ZodString>;
336
+ to: z.ZodOptional<z.ZodString>;
337
+ intended: z.ZodOptional<z.ZodObject<{
338
+ from: z.ZodOptional<z.ZodString>;
339
+ to: z.ZodOptional<z.ZodString>;
340
+ }, z.core.$loose>>;
341
+ source: z.ZodOptional<z.ZodString>;
342
+ outcome: z.ZodOptional<z.ZodString>;
343
+ instrumentation: z.ZodOptional<z.ZodString>;
344
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
323
345
  }, z.core.$loose>>;
324
346
  testId: z.ZodNullable<z.ZodString>;
325
347
  variant: z.ZodRecord<z.ZodString, z.ZodString>;
@@ -390,6 +412,17 @@ export declare const LayerEvidenceSchema: z.ZodObject<{
390
412
  value: z.ZodNumber;
391
413
  unit: z.ZodString;
392
414
  limit: z.ZodOptional<z.ZodNumber>;
415
+ kind: z.ZodOptional<z.ZodString>;
416
+ from: z.ZodOptional<z.ZodString>;
417
+ to: z.ZodOptional<z.ZodString>;
418
+ intended: z.ZodOptional<z.ZodObject<{
419
+ from: z.ZodOptional<z.ZodString>;
420
+ to: z.ZodOptional<z.ZodString>;
421
+ }, z.core.$loose>>;
422
+ source: z.ZodOptional<z.ZodString>;
423
+ outcome: z.ZodOptional<z.ZodString>;
424
+ instrumentation: z.ZodOptional<z.ZodString>;
425
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
393
426
  }, z.core.$loose>>;
394
427
  testId: z.ZodNullable<z.ZodString>;
395
428
  variant: z.ZodRecord<z.ZodString, z.ZodString>;
@@ -544,6 +577,17 @@ export declare const EffectiveEvidenceSchema: z.ZodObject<{
544
577
  value: z.ZodNumber;
545
578
  unit: z.ZodString;
546
579
  limit: z.ZodOptional<z.ZodNumber>;
580
+ kind: z.ZodOptional<z.ZodString>;
581
+ from: z.ZodOptional<z.ZodString>;
582
+ to: z.ZodOptional<z.ZodString>;
583
+ intended: z.ZodOptional<z.ZodObject<{
584
+ from: z.ZodOptional<z.ZodString>;
585
+ to: z.ZodOptional<z.ZodString>;
586
+ }, z.core.$loose>>;
587
+ source: z.ZodOptional<z.ZodString>;
588
+ outcome: z.ZodOptional<z.ZodString>;
589
+ instrumentation: z.ZodOptional<z.ZodString>;
590
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
547
591
  }, z.core.$loose>>;
548
592
  testId: z.ZodNullable<z.ZodString>;
549
593
  variant: z.ZodRecord<z.ZodString, z.ZodString>;
@@ -719,6 +763,17 @@ export declare const EffectiveDeltaSchema: z.ZodObject<{
719
763
  value: z.ZodNumber;
720
764
  unit: z.ZodString;
721
765
  limit: z.ZodOptional<z.ZodNumber>;
766
+ kind: z.ZodOptional<z.ZodString>;
767
+ from: z.ZodOptional<z.ZodString>;
768
+ to: z.ZodOptional<z.ZodString>;
769
+ intended: z.ZodOptional<z.ZodObject<{
770
+ from: z.ZodOptional<z.ZodString>;
771
+ to: z.ZodOptional<z.ZodString>;
772
+ }, z.core.$loose>>;
773
+ source: z.ZodOptional<z.ZodString>;
774
+ outcome: z.ZodOptional<z.ZodString>;
775
+ instrumentation: z.ZodOptional<z.ZodString>;
776
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
722
777
  }, z.core.$loose>>;
723
778
  testId: z.ZodNullable<z.ZodString>;
724
779
  variant: z.ZodRecord<z.ZodString, z.ZodString>;
@@ -1312,6 +1367,17 @@ export declare const CheckDetailSchema: z.ZodObject<{
1312
1367
  value: z.ZodNumber;
1313
1368
  unit: z.ZodString;
1314
1369
  limit: z.ZodOptional<z.ZodNumber>;
1370
+ kind: z.ZodOptional<z.ZodString>;
1371
+ from: z.ZodOptional<z.ZodString>;
1372
+ to: z.ZodOptional<z.ZodString>;
1373
+ intended: z.ZodOptional<z.ZodObject<{
1374
+ from: z.ZodOptional<z.ZodString>;
1375
+ to: z.ZodOptional<z.ZodString>;
1376
+ }, z.core.$loose>>;
1377
+ source: z.ZodOptional<z.ZodString>;
1378
+ outcome: z.ZodOptional<z.ZodString>;
1379
+ instrumentation: z.ZodOptional<z.ZodString>;
1380
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1315
1381
  }, z.core.$loose>>>;
1316
1382
  durationMs: z.ZodOptional<z.ZodNumber>;
1317
1383
  recordingExpectation: z.ZodOptional<z.ZodString>;
@@ -1498,6 +1564,23 @@ export declare const TestResultEntrySchema: z.ZodObject<{
1498
1564
  }, z.core.$loose>>;
1499
1565
  }, z.core.$loose>>>;
1500
1566
  }, z.core.$loose>>;
1567
+ latency: z.ZodOptional<z.ZodArray<z.ZodObject<{
1568
+ name: z.ZodString;
1569
+ value: z.ZodNumber;
1570
+ unit: z.ZodString;
1571
+ limit: z.ZodOptional<z.ZodNumber>;
1572
+ kind: z.ZodOptional<z.ZodString>;
1573
+ from: z.ZodOptional<z.ZodString>;
1574
+ to: z.ZodOptional<z.ZodString>;
1575
+ intended: z.ZodOptional<z.ZodObject<{
1576
+ from: z.ZodOptional<z.ZodString>;
1577
+ to: z.ZodOptional<z.ZodString>;
1578
+ }, z.core.$loose>>;
1579
+ source: z.ZodOptional<z.ZodString>;
1580
+ outcome: z.ZodOptional<z.ZodString>;
1581
+ instrumentation: z.ZodOptional<z.ZodString>;
1582
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1583
+ }, z.core.$loose>>>;
1501
1584
  }, z.core.$loose>;
1502
1585
  /** One test of the catalog: what it is, its current result per environment, and how it has gone over its recorded history. */
1503
1586
  export declare const TestSummarySchema: z.ZodObject<{
@@ -1578,6 +1661,23 @@ export declare const TestSummarySchema: z.ZodObject<{
1578
1661
  }, z.core.$loose>>;
1579
1662
  }, z.core.$loose>>>;
1580
1663
  }, z.core.$loose>>;
1664
+ latency: z.ZodOptional<z.ZodArray<z.ZodObject<{
1665
+ name: z.ZodString;
1666
+ value: z.ZodNumber;
1667
+ unit: z.ZodString;
1668
+ limit: z.ZodOptional<z.ZodNumber>;
1669
+ kind: z.ZodOptional<z.ZodString>;
1670
+ from: z.ZodOptional<z.ZodString>;
1671
+ to: z.ZodOptional<z.ZodString>;
1672
+ intended: z.ZodOptional<z.ZodObject<{
1673
+ from: z.ZodOptional<z.ZodString>;
1674
+ to: z.ZodOptional<z.ZodString>;
1675
+ }, z.core.$loose>>;
1676
+ source: z.ZodOptional<z.ZodString>;
1677
+ outcome: z.ZodOptional<z.ZodString>;
1678
+ instrumentation: z.ZodOptional<z.ZodString>;
1679
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1680
+ }, z.core.$loose>>>;
1581
1681
  }, z.core.$loose>>;
1582
1682
  stats: z.ZodObject<{
1583
1683
  results: z.ZodNumber;
@@ -1853,6 +1953,23 @@ export declare const TestCatalogSchema: z.ZodObject<{
1853
1953
  }, z.core.$loose>>;
1854
1954
  }, z.core.$loose>>>;
1855
1955
  }, z.core.$loose>>;
1956
+ latency: z.ZodOptional<z.ZodArray<z.ZodObject<{
1957
+ name: z.ZodString;
1958
+ value: z.ZodNumber;
1959
+ unit: z.ZodString;
1960
+ limit: z.ZodOptional<z.ZodNumber>;
1961
+ kind: z.ZodOptional<z.ZodString>;
1962
+ from: z.ZodOptional<z.ZodString>;
1963
+ to: z.ZodOptional<z.ZodString>;
1964
+ intended: z.ZodOptional<z.ZodObject<{
1965
+ from: z.ZodOptional<z.ZodString>;
1966
+ to: z.ZodOptional<z.ZodString>;
1967
+ }, z.core.$loose>>;
1968
+ source: z.ZodOptional<z.ZodString>;
1969
+ outcome: z.ZodOptional<z.ZodString>;
1970
+ instrumentation: z.ZodOptional<z.ZodString>;
1971
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1972
+ }, z.core.$loose>>>;
1856
1973
  }, z.core.$loose>>;
1857
1974
  stats: z.ZodObject<{
1858
1975
  results: z.ZodNumber;
@@ -2055,6 +2172,23 @@ export declare const TestHistorySchema: z.ZodObject<{
2055
2172
  }, z.core.$loose>>;
2056
2173
  }, z.core.$loose>>>;
2057
2174
  }, z.core.$loose>>;
2175
+ latency: z.ZodOptional<z.ZodArray<z.ZodObject<{
2176
+ name: z.ZodString;
2177
+ value: z.ZodNumber;
2178
+ unit: z.ZodString;
2179
+ limit: z.ZodOptional<z.ZodNumber>;
2180
+ kind: z.ZodOptional<z.ZodString>;
2181
+ from: z.ZodOptional<z.ZodString>;
2182
+ to: z.ZodOptional<z.ZodString>;
2183
+ intended: z.ZodOptional<z.ZodObject<{
2184
+ from: z.ZodOptional<z.ZodString>;
2185
+ to: z.ZodOptional<z.ZodString>;
2186
+ }, z.core.$loose>>;
2187
+ source: z.ZodOptional<z.ZodString>;
2188
+ outcome: z.ZodOptional<z.ZodString>;
2189
+ instrumentation: z.ZodOptional<z.ZodString>;
2190
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
2191
+ }, z.core.$loose>>>;
2058
2192
  }, z.core.$loose>>;
2059
2193
  stats: z.ZodObject<{
2060
2194
  results: z.ZodNumber;
@@ -2154,6 +2288,23 @@ export declare const TestHistorySchema: z.ZodObject<{
2154
2288
  }, z.core.$loose>>;
2155
2289
  }, z.core.$loose>>>;
2156
2290
  }, z.core.$loose>>;
2291
+ latency: z.ZodOptional<z.ZodArray<z.ZodObject<{
2292
+ name: z.ZodString;
2293
+ value: z.ZodNumber;
2294
+ unit: z.ZodString;
2295
+ limit: z.ZodOptional<z.ZodNumber>;
2296
+ kind: z.ZodOptional<z.ZodString>;
2297
+ from: z.ZodOptional<z.ZodString>;
2298
+ to: z.ZodOptional<z.ZodString>;
2299
+ intended: z.ZodOptional<z.ZodObject<{
2300
+ from: z.ZodOptional<z.ZodString>;
2301
+ to: z.ZodOptional<z.ZodString>;
2302
+ }, z.core.$loose>>;
2303
+ source: z.ZodOptional<z.ZodString>;
2304
+ outcome: z.ZodOptional<z.ZodString>;
2305
+ instrumentation: z.ZodOptional<z.ZodString>;
2306
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
2307
+ }, z.core.$loose>>>;
2157
2308
  }, z.core.$loose>>;
2158
2309
  }, z.core.$loose>;
2159
2310
  /** A run: the layers that name the same execution (else share a run URL, else a branch and commit), with what they recorded. */
@@ -14,7 +14,7 @@
14
14
  * @see docs/server.md
15
15
  */
16
16
  import { z } from 'zod/v4';
17
- import { CheckSchema, EvidenceScopeSchema, LayerRunSchema } from './layer.js';
17
+ import { CheckSchema, EvidenceScopeSchema, LayerRunSchema, MeasurementSchema } from './layer.js';
18
18
  /** One of the statuses a check has in a report: `passed`, `failed`, `running` (not finished) or `not-run`. Closed. */
19
19
  export const EvidenceStatusSchema = z.enum(['passed', 'failed', 'running', 'not-run']);
20
20
  /**
@@ -124,7 +124,7 @@ export const LayerRowSchema = z.looseObject({
124
124
  status: EvidenceStatusSchema,
125
125
  specPath: z.string().nullable().describe('The spec the check belongs to, `specs/<path>`, or null'),
126
126
  detail: z.string().describe('Why it has this status; empty when there is nothing to add'),
127
- measurements: z.array(z.looseObject({ name: z.string(), value: z.number(), unit: z.string(), limit: z.number().optional() })),
127
+ measurements: z.array(MeasurementSchema),
128
128
  testId: z.string().nullable(),
129
129
  variant: z.record(z.string(), z.string()),
130
130
  durationMs: z.number().nullable(),
@@ -270,7 +270,8 @@ export const TestResultEntrySchema = z.looseObject({
270
270
  layer: z.string().describe('The id of the layer that recorded it'),
271
271
  backend: z.string(),
272
272
  detail: z.string().describe('Why it has its status, at most 500 characters'),
273
- evidence: EvidenceSummarySchema.optional()
273
+ evidence: EvidenceSummarySchema.optional(),
274
+ latency: z.array(MeasurementSchema).optional().describe('Its product-latency measurements (`kind: product-latency`), when it recorded any. Separate from `durationMs`, which is how long the test took')
274
275
  });
275
276
  const DurationStatsSchema = z.looseObject({
276
277
  count: z.number().int().nonnegative(), totalMs: z.number(), avgMs: z.number(), p50Ms: z.number(), p95Ms: z.number(), minMs: z.number(), maxMs: z.number(), lastMs: z.number()
@@ -74,16 +74,40 @@ export declare const LayerRunSchema: z.ZodObject<{
74
74
  }, z.core.$loose>;
75
75
  /** The execution of a layer, parsed from `LayerRunSchema`. */
76
76
  export type LayerRun = z.output<typeof LayerRunSchema>;
77
+ /** The `kind` of a measurement that is a named, user-visible product latency (see docs/rich-evidence.md#product-latency). */
78
+ export declare const PRODUCT_LATENCY_KIND = "product-latency";
79
+ /** The two ends of a measured interval, named in the producer's words: `{ from: 'submit pressed', to: 'confirmation visible' }`. */
80
+ export declare const MeasurementBoundarySchema: z.ZodObject<{
81
+ from: z.ZodOptional<z.ZodString>;
82
+ to: z.ZodOptional<z.ZodString>;
83
+ }, z.core.$loose>;
77
84
  /**
78
85
  * A number a check measured, such as a latency or a size, with its unit and an optional allowance. A measurement over
79
86
  * its `limit` is reported and never changes the check's status.
87
+ *
88
+ * A measurement of `kind` `product-latency` is how quickly the product responded to a user, between two named events.
89
+ * It is not the check's duration (which includes setup, waits and pacing) and a number with no `kind` is never read as
90
+ * product latency. All the fields after `limit` are optional and describe the measurement; none decides a status.
80
91
  */
81
92
  export declare const MeasurementSchema: z.ZodObject<{
82
93
  name: z.ZodString;
83
94
  value: z.ZodNumber;
84
95
  unit: z.ZodString;
85
96
  limit: z.ZodOptional<z.ZodNumber>;
97
+ kind: z.ZodOptional<z.ZodString>;
98
+ from: z.ZodOptional<z.ZodString>;
99
+ to: z.ZodOptional<z.ZodString>;
100
+ intended: z.ZodOptional<z.ZodObject<{
101
+ from: z.ZodOptional<z.ZodString>;
102
+ to: z.ZodOptional<z.ZodString>;
103
+ }, z.core.$loose>>;
104
+ source: z.ZodOptional<z.ZodString>;
105
+ outcome: z.ZodOptional<z.ZodString>;
106
+ instrumentation: z.ZodOptional<z.ZodString>;
107
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
86
108
  }, z.core.$loose>;
109
+ /** A measurement as stored in a layer, parsed from `MeasurementSchema`. */
110
+ export type Measurement = z.output<typeof MeasurementSchema>;
87
111
  /**
88
112
  * Structured data of a producer's or tool's own, by namespace: `{ "com.example.coverage": { … } }`.
89
113
  * Use a name you own (a reverse domain, or your tool's name). Readers preserve it and never fail on it.
@@ -867,6 +891,17 @@ export declare const CheckSchema: z.ZodObject<{
867
891
  value: z.ZodNumber;
868
892
  unit: z.ZodString;
869
893
  limit: z.ZodOptional<z.ZodNumber>;
894
+ kind: z.ZodOptional<z.ZodString>;
895
+ from: z.ZodOptional<z.ZodString>;
896
+ to: z.ZodOptional<z.ZodString>;
897
+ intended: z.ZodOptional<z.ZodObject<{
898
+ from: z.ZodOptional<z.ZodString>;
899
+ to: z.ZodOptional<z.ZodString>;
900
+ }, z.core.$loose>>;
901
+ source: z.ZodOptional<z.ZodString>;
902
+ outcome: z.ZodOptional<z.ZodString>;
903
+ instrumentation: z.ZodOptional<z.ZodString>;
904
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
870
905
  }, z.core.$loose>>>;
871
906
  durationMs: z.ZodOptional<z.ZodNumber>;
872
907
  recordingExpectation: z.ZodOptional<z.ZodString>;
@@ -1338,6 +1373,17 @@ export declare const ReportPartSchema: z.ZodObject<{
1338
1373
  value: z.ZodNumber;
1339
1374
  unit: z.ZodString;
1340
1375
  limit: z.ZodOptional<z.ZodNumber>;
1376
+ kind: z.ZodOptional<z.ZodString>;
1377
+ from: z.ZodOptional<z.ZodString>;
1378
+ to: z.ZodOptional<z.ZodString>;
1379
+ intended: z.ZodOptional<z.ZodObject<{
1380
+ from: z.ZodOptional<z.ZodString>;
1381
+ to: z.ZodOptional<z.ZodString>;
1382
+ }, z.core.$loose>>;
1383
+ source: z.ZodOptional<z.ZodString>;
1384
+ outcome: z.ZodOptional<z.ZodString>;
1385
+ instrumentation: z.ZodOptional<z.ZodString>;
1386
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1341
1387
  }, z.core.$loose>>>;
1342
1388
  durationMs: z.ZodOptional<z.ZodNumber>;
1343
1389
  recordingExpectation: z.ZodOptional<z.ZodString>;
@@ -1708,6 +1754,17 @@ export declare const LayerSourceSchema: z.ZodObject<{
1708
1754
  value: z.ZodNumber;
1709
1755
  unit: z.ZodString;
1710
1756
  limit: z.ZodOptional<z.ZodNumber>;
1757
+ kind: z.ZodOptional<z.ZodString>;
1758
+ from: z.ZodOptional<z.ZodString>;
1759
+ to: z.ZodOptional<z.ZodString>;
1760
+ intended: z.ZodOptional<z.ZodObject<{
1761
+ from: z.ZodOptional<z.ZodString>;
1762
+ to: z.ZodOptional<z.ZodString>;
1763
+ }, z.core.$loose>>;
1764
+ source: z.ZodOptional<z.ZodString>;
1765
+ outcome: z.ZodOptional<z.ZodString>;
1766
+ instrumentation: z.ZodOptional<z.ZodString>;
1767
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1711
1768
  }, z.core.$loose>>>;
1712
1769
  durationMs: z.ZodOptional<z.ZodNumber>;
1713
1770
  recordingExpectation: z.ZodOptional<z.ZodString>;
@@ -2324,6 +2381,17 @@ export declare const LayerDocumentSchema: z.ZodObject<{
2324
2381
  value: z.ZodNumber;
2325
2382
  unit: z.ZodString;
2326
2383
  limit: z.ZodOptional<z.ZodNumber>;
2384
+ kind: z.ZodOptional<z.ZodString>;
2385
+ from: z.ZodOptional<z.ZodString>;
2386
+ to: z.ZodOptional<z.ZodString>;
2387
+ intended: z.ZodOptional<z.ZodObject<{
2388
+ from: z.ZodOptional<z.ZodString>;
2389
+ to: z.ZodOptional<z.ZodString>;
2390
+ }, z.core.$loose>>;
2391
+ source: z.ZodOptional<z.ZodString>;
2392
+ outcome: z.ZodOptional<z.ZodString>;
2393
+ instrumentation: z.ZodOptional<z.ZodString>;
2394
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
2327
2395
  }, z.core.$loose>>>;
2328
2396
  durationMs: z.ZodOptional<z.ZodNumber>;
2329
2397
  recordingExpectation: z.ZodOptional<z.ZodString>;
@@ -2693,6 +2761,17 @@ export declare const LayerDocumentSchema: z.ZodObject<{
2693
2761
  value: z.ZodNumber;
2694
2762
  unit: z.ZodString;
2695
2763
  limit: z.ZodOptional<z.ZodNumber>;
2764
+ kind: z.ZodOptional<z.ZodString>;
2765
+ from: z.ZodOptional<z.ZodString>;
2766
+ to: z.ZodOptional<z.ZodString>;
2767
+ intended: z.ZodOptional<z.ZodObject<{
2768
+ from: z.ZodOptional<z.ZodString>;
2769
+ to: z.ZodOptional<z.ZodString>;
2770
+ }, z.core.$loose>>;
2771
+ source: z.ZodOptional<z.ZodString>;
2772
+ outcome: z.ZodOptional<z.ZodString>;
2773
+ instrumentation: z.ZodOptional<z.ZodString>;
2774
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
2696
2775
  }, z.core.$loose>>>;
2697
2776
  durationMs: z.ZodOptional<z.ZodNumber>;
2698
2777
  recordingExpectation: z.ZodOptional<z.ZodString>;
@@ -2901,6 +2980,17 @@ export declare const PublishChecksSchema: z.ZodObject<{
2901
2980
  value: z.ZodNumber;
2902
2981
  unit: z.ZodString;
2903
2982
  limit: z.ZodOptional<z.ZodNumber>;
2983
+ kind: z.ZodOptional<z.ZodString>;
2984
+ from: z.ZodOptional<z.ZodString>;
2985
+ to: z.ZodOptional<z.ZodString>;
2986
+ intended: z.ZodOptional<z.ZodObject<{
2987
+ from: z.ZodOptional<z.ZodString>;
2988
+ to: z.ZodOptional<z.ZodString>;
2989
+ }, z.core.$loose>>;
2990
+ source: z.ZodOptional<z.ZodString>;
2991
+ outcome: z.ZodOptional<z.ZodString>;
2992
+ instrumentation: z.ZodOptional<z.ZodString>;
2993
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
2904
2994
  }, z.core.$loose>>>;
2905
2995
  durationMs: z.ZodOptional<z.ZodNumber>;
2906
2996
  recordingExpectation: z.ZodOptional<z.ZodString>;
@@ -62,15 +62,34 @@ export const LayerRunSchema = z.looseObject({
62
62
  startedAtMs: z.number().nonnegative().optional().describe('When the execution started, epoch milliseconds'),
63
63
  endedAtMs: z.number().nonnegative().optional().describe('When the execution ended, epoch milliseconds')
64
64
  });
65
+ /** The `kind` of a measurement that is a named, user-visible product latency (see docs/rich-evidence.md#product-latency). */
66
+ export const PRODUCT_LATENCY_KIND = 'product-latency';
67
+ /** The two ends of a measured interval, named in the producer's words: `{ from: 'submit pressed', to: 'confirmation visible' }`. */
68
+ export const MeasurementBoundarySchema = z.looseObject({
69
+ from: z.string().min(1).optional().describe('The event the interval starts at: `end of speech`, `submit pressed`'),
70
+ to: z.string().min(1).optional().describe('The event the interval ends at: `audible response`, `confirmation visible`')
71
+ });
65
72
  /**
66
73
  * A number a check measured, such as a latency or a size, with its unit and an optional allowance. A measurement over
67
74
  * its `limit` is reported and never changes the check's status.
75
+ *
76
+ * A measurement of `kind` `product-latency` is how quickly the product responded to a user, between two named events.
77
+ * It is not the check's duration (which includes setup, waits and pacing) and a number with no `kind` is never read as
78
+ * product latency. All the fields after `limit` are optional and describe the measurement; none decides a status.
68
79
  */
69
80
  export const MeasurementSchema = z.looseObject({
70
81
  name: z.string().min(1),
71
82
  value: z.number().describe('A finite number'),
72
83
  unit: z.string(),
73
- limit: z.number().optional().describe('The allowance. A measurement over it is reported, and never fails the check by itself')
84
+ limit: z.number().optional().describe('The allowance, or budget. A measurement over it is reported, and never fails the check by itself'),
85
+ kind: z.string().min(1).optional().describe('What the number is, in an open vocabulary. `product-latency`: a user-visible response time between `from` and `to`. Absent: just a number, never read as product latency'),
86
+ from: z.string().min(1).optional().describe('The event the interval starts at. Without both `from` and `to` a latency is shown but no product claim is made from it'),
87
+ to: z.string().min(1).optional().describe('The event the interval ends at'),
88
+ intended: MeasurementBoundarySchema.optional().describe('The interval the name of the measurement claims, when what was measured is only part of it. If `intended.to` differs from `to` (or `intended.from` from `from`) the measurement is shown as partial, never as the complete interval'),
89
+ source: z.string().optional().describe('How it was measured: `app-instrumentation`, `trace`, `recording-analysis`'),
90
+ outcome: z.string().min(1).optional().describe('`observed` (the default), `timeout` or `failed`. For anything but `observed` the value is how long the observation waited: at least that much, so it counts against a budget and is never dropped'),
91
+ instrumentation: z.string().optional().describe('The version or method of the measurement. A change of it between two sets of observations is disclosed when they are compared'),
92
+ labels: z.record(z.string(), z.string()).optional().describe('What the observation is comparable with: `device`, `build`, `network`. `device` and `build` group the observations and, when two groups differ in the others, the comparison says so')
74
93
  });
75
94
  /**
76
95
  * Structured data of a producer's or tool's own, by namespace: `{ "com.example.coverage": { … } }`.
package/docs/extending.md CHANGED
@@ -34,7 +34,7 @@ carry any of these. Every one is optional.
34
34
  | `scope{}` | What the evidence does and does not show: surface, device kind, synthetic data, scripted dependencies, audio, pacing, what was asserted and its limits. Unknown when absent. See [Evidence scope](rich-evidence.md#evidence-scope). |
35
35
  | `links[]` | Where to read more: the CI run, the issue, the trace client. |
36
36
  | `extensions{}` | Structured data of your own or a tool's, under a name you own. |
37
- | `measurements[]` | Numbers, with units and an allowance (already part of the contract). |
37
+ | `measurements[]` | Numbers, with units and an allowance (already part of the contract). A measurement of `kind` `product-latency` names a user-visible response time between two events; see [Product latency](rich-evidence.md#product-latency). |
38
38
 
39
39
  An attachment may carry its own `tags[]` (`demo`): shown as `#demo` and searchable; see
40
40
  [Tagging artifacts and demos](rich-evidence.md#tagging-artifacts-and-demos).