@crvouga/atlas 1.6.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.
- package/CHANGELOG.md +13 -1
- package/dist/reports/src/explorer-types.d.ts +19 -0
- package/dist/reports/src/latency.d.ts +204 -0
- package/dist/reports/src/latency.js +219 -0
- package/dist/reports/src/reports-explorer-route.d.ts +19 -6
- package/dist/reports/src/reports-route.d.ts +19 -6
- package/dist/reports/src/test-catalog.d.ts +3 -1
- package/dist/reports/src/test-catalog.js +4 -2
- package/dist/schema/src/api.d.ts +151 -0
- package/dist/schema/src/api.js +4 -3
- package/dist/schema/src/layer.d.ts +90 -0
- package/dist/schema/src/layer.js +20 -1
- package/docs/extending.md +18 -1
- package/docs/reference/reports.md +347 -0
- package/docs/reference/schema.md +28 -0
- package/docs/rich-evidence.md +43 -0
- package/docs/ui.md +24 -1
- package/llms-full.txt +85 -2
- package/package.json +1 -1
- package/packages/schema/schemas/check-detail.schema.json +56 -1
- package/packages/schema/schemas/effective-delta.schema.json +60 -2
- package/packages/schema/schemas/effective-evidence.schema.json +60 -2
- package/packages/schema/schemas/layer-rows.schema.json +60 -2
- package/packages/schema/schemas/layer.schema.json +112 -2
- package/packages/schema/schemas/publish-checks.schema.json +56 -1
- package/packages/schema/schemas/test-catalog.schema.json +85 -0
- package/packages/schema/schemas/test-history.schema.json +170 -0
- package/packages/server/openapi.json +547 -8
- package/packages/visualizer/src/evidence/behavior-panel.tsx +10 -16
- package/packages/visualizer/src/evidence/check-detail.tsx +59 -15
- package/packages/visualizer/src/evidence/outcome-summary.tsx +208 -0
- package/packages/visualizer/src/evidence/outcome.test.ts +129 -0
- package/packages/visualizer/src/evidence/outcome.ts +309 -0
- package/packages/visualizer/src/explorer/ProductLatency.tsx +214 -0
- package/packages/visualizer/src/explorer/TestDetail.tsx +32 -3
- package/packages/visualizer/src/explorer/search.ts +3 -0
|
@@ -2608,6 +2608,22 @@ export type EvidenceMeasurement = {
|
|
|
2608
2608
|
readonly unit: string;
|
|
2609
2609
|
/** The allowance for `value`, in the same unit, when there is one. */
|
|
2610
2610
|
readonly limit?: number;
|
|
2611
|
+
/** What the number is, in an open vocabulary. `product-latency` is a user-visible response time between `from` and `to`; absent is just a number. */
|
|
2612
|
+
readonly kind?: string;
|
|
2613
|
+
/** The event the measured interval starts at. */
|
|
2614
|
+
readonly from?: string;
|
|
2615
|
+
/** The event the measured interval ends at. */
|
|
2616
|
+
readonly to?: string;
|
|
2617
|
+
/** The interval the name claims, when the measured one is only part of it. */
|
|
2618
|
+
readonly intended?: { readonly from?: string; readonly to?: string };
|
|
2619
|
+
/** How it was measured. */
|
|
2620
|
+
readonly source?: string;
|
|
2621
|
+
/** `observed` (default), `timeout`, `failed`; otherwise `value` is how long the observation waited, at least. */
|
|
2622
|
+
readonly outcome?: string;
|
|
2623
|
+
/** The version or method of the measurement. */
|
|
2624
|
+
readonly instrumentation?: string;
|
|
2625
|
+
/** What the observation is comparable with: `device`, `build`. */
|
|
2626
|
+
readonly labels?: Readonly<Record<string, string>>;
|
|
2611
2627
|
};
|
|
2612
2628
|
```
|
|
2613
2629
|
|
|
@@ -4073,6 +4089,335 @@ export function splitViewerTestId(
|
|
|
4073
4089
|
|
|
4074
4090
|
The inverse of {@link viewerTestId}: splits at the first `/`; null when either side is not a safe key segment.
|
|
4075
4091
|
|
|
4092
|
+
### Product latency
|
|
4093
|
+
|
|
4094
|
+
Source: `packages/reports/src/latency.ts`. Import: `@crvouga/atlas/reports/latency`
|
|
4095
|
+
|
|
4096
|
+
Tells how quickly a product responded apart from how long a test took. A test's `durationMs` includes fixture setup,
|
|
4097
|
+
deliberate waits, demo pacing, retries and cleanup, so it says nothing about a user's wait. Product latency is only
|
|
4098
|
+
what a producer measured and named as such: a measurement of `kind` `product-latency` between two named events
|
|
4099
|
+
(`from`, `to`), optionally with a budget (`limit`), an outcome (`observed`, `timeout`, `failed`), the instrumentation,
|
|
4100
|
+
and labels (`device`, `build`) in its `labels`. Pure functions, safe in a browser.
|
|
4101
|
+
|
|
4102
|
+
The rules, each of which keeps a number from looking healthier or more certain than the evidence is:
|
|
4103
|
+
|
|
4104
|
+
- **Only named measurements are latency.** A check's duration, a video's length, a recorded step and a number with no
|
|
4105
|
+
`kind` are never product latency. No latency recorded is unknown, not fast.
|
|
4106
|
+
- **Boundaries are part of the claim.** Without both `from` and `to`, nothing is claimed from the numbers. When
|
|
4107
|
+
`intended` names a longer interval than the one measured (speech end to TTS dispatch, claimed as microphone to
|
|
4108
|
+
audible response) the measurement is partial and says so.
|
|
4109
|
+
- **Percentiles need samples.** p50 needs {@link MIN_P50_SAMPLES} observations and p95 {@link MIN_P95_SAMPLES}; below
|
|
4110
|
+
that they are withheld with the reason, never shown as if supported.
|
|
4111
|
+
- **A timeout or failed observation is a sample.** Its value is how long it waited, so a lower bound: it counts
|
|
4112
|
+
in the sample, in the percentiles and as a budget breach, and the summary says the figures are lower bounds.
|
|
4113
|
+
- **Comparisons are of the same metric.** Different boundaries are not compared; a change of instrumentation,
|
|
4114
|
+
environment, budget or other condition between the two sides is disclosed beside the change.
|
|
4115
|
+
|
|
4116
|
+
It decides nothing: a latency, a budget breach and a comparison never change a status, a slot or a winner.
|
|
4117
|
+
|
|
4118
|
+
See docs/rich-evidence.md#product-latency
|
|
4119
|
+
|
|
4120
|
+
#### `PRODUCT_LATENCY`
|
|
4121
|
+
|
|
4122
|
+
```ts
|
|
4123
|
+
export const PRODUCT_LATENCY = …
|
|
4124
|
+
```
|
|
4125
|
+
|
|
4126
|
+
The `kind` of a measurement that is a user-visible product latency.
|
|
4127
|
+
|
|
4128
|
+
#### `MIN_P50_SAMPLES`
|
|
4129
|
+
|
|
4130
|
+
```ts
|
|
4131
|
+
export const MIN_P50_SAMPLES = …
|
|
4132
|
+
```
|
|
4133
|
+
|
|
4134
|
+
Observations needed before a p50 is shown.
|
|
4135
|
+
|
|
4136
|
+
#### `MIN_P95_SAMPLES`
|
|
4137
|
+
|
|
4138
|
+
```ts
|
|
4139
|
+
export const MIN_P95_SAMPLES = …
|
|
4140
|
+
```
|
|
4141
|
+
|
|
4142
|
+
Observations needed before a p95 is shown.
|
|
4143
|
+
|
|
4144
|
+
#### `isProductLatency`
|
|
4145
|
+
|
|
4146
|
+
```ts
|
|
4147
|
+
export function isProductLatency(measurement: Pick<EvidenceMeasurement, 'kind'>): boolean
|
|
4148
|
+
```
|
|
4149
|
+
|
|
4150
|
+
Whether a measurement is a product latency: only the producer's `kind` makes it one. A name, a unit or a value never does.
|
|
4151
|
+
|
|
4152
|
+
#### `productLatency`
|
|
4153
|
+
|
|
4154
|
+
```ts
|
|
4155
|
+
export function productLatency(measurements: readonly EvidenceMeasurement[] | undefined): readonly EvidenceMeasurement[] | undefined
|
|
4156
|
+
```
|
|
4157
|
+
|
|
4158
|
+
The product-latency measurements of a check, or undefined when it has none (so a result with no latency carries
|
|
4159
|
+
nothing). Measurements that are not well formed (no finite value, no name) are left out.
|
|
4160
|
+
|
|
4161
|
+
#### `BoundaryState`
|
|
4162
|
+
|
|
4163
|
+
```ts
|
|
4164
|
+
export type BoundaryState = 'declared' | 'partial' | 'undeclared';
|
|
4165
|
+
```
|
|
4166
|
+
|
|
4167
|
+
How well the ends of a measurement are known: `declared` (both named), `partial` (it stops short of, or starts after, what it claims) or `undeclared`.
|
|
4168
|
+
|
|
4169
|
+
#### `Boundary`
|
|
4170
|
+
|
|
4171
|
+
```ts
|
|
4172
|
+
export type Boundary = {
|
|
4173
|
+
readonly state: BoundaryState;
|
|
4174
|
+
readonly from?: string;
|
|
4175
|
+
readonly to?: string;
|
|
4176
|
+
/** The interval the measurement's name claims, when it is longer than what was measured. */
|
|
4177
|
+
readonly intended?: string;
|
|
4178
|
+
/** `submit pressed to confirmation visible`, `to confirmation visible (start not declared)`. */
|
|
4179
|
+
readonly label: string;
|
|
4180
|
+
};
|
|
4181
|
+
```
|
|
4182
|
+
|
|
4183
|
+
The ends of a measurement, in words, and whether they support a claim about the user's wait.
|
|
4184
|
+
|
|
4185
|
+
#### `boundaryOf`
|
|
4186
|
+
|
|
4187
|
+
```ts
|
|
4188
|
+
export function boundaryOf(measurement: Pick<EvidenceMeasurement, 'from' | 'to' | 'intended'>): Boundary
|
|
4189
|
+
```
|
|
4190
|
+
|
|
4191
|
+
What a measurement spans. `undeclared` when either end is missing: the numbers are shown and no product claim is made.
|
|
4192
|
+
`partial` when `intended` names a different end than the measured one: the interval is the measured one, never the intended.
|
|
4193
|
+
|
|
4194
|
+
#### `ObservationOutcome`
|
|
4195
|
+
|
|
4196
|
+
```ts
|
|
4197
|
+
export type ObservationOutcome = 'observed' | 'timeout' | 'failed' | 'unclear';
|
|
4198
|
+
```
|
|
4199
|
+
|
|
4200
|
+
What happened to one observation: `observed`, or it `timeout`ed or `failed`, or the producer said something Atlas cannot read (`unclear`).
|
|
4201
|
+
|
|
4202
|
+
#### `LatencyEntry`
|
|
4203
|
+
|
|
4204
|
+
```ts
|
|
4205
|
+
export type LatencyEntry = {
|
|
4206
|
+
/** When it was recorded, epoch milliseconds. */
|
|
4207
|
+
readonly at: number;
|
|
4208
|
+
readonly environment: string | null;
|
|
4209
|
+
readonly parameters: Readonly<Record<string, string>>;
|
|
4210
|
+
readonly sha: string | null;
|
|
4211
|
+
readonly layer: string;
|
|
4212
|
+
readonly run?: string;
|
|
4213
|
+
readonly latency?: readonly EvidenceMeasurement[];
|
|
4214
|
+
readonly evidence?: { readonly labels: Readonly<Record<string, string>> };
|
|
4215
|
+
};
|
|
4216
|
+
```
|
|
4217
|
+
|
|
4218
|
+
One recorded result, as much of it as this module reads. A `TestResultEntry` is one.
|
|
4219
|
+
|
|
4220
|
+
#### `LatencyObservation`
|
|
4221
|
+
|
|
4222
|
+
```ts
|
|
4223
|
+
export type LatencyObservation = {
|
|
4224
|
+
readonly name: string;
|
|
4225
|
+
readonly unit: string;
|
|
4226
|
+
readonly value: number;
|
|
4227
|
+
readonly limit?: number;
|
|
4228
|
+
readonly outcome: ObservationOutcome;
|
|
4229
|
+
readonly boundary: Boundary;
|
|
4230
|
+
readonly source?: string;
|
|
4231
|
+
readonly instrumentation?: string;
|
|
4232
|
+
readonly cohort: Readonly<Record<string, string>>;
|
|
4233
|
+
readonly at: number;
|
|
4234
|
+
readonly layer: string;
|
|
4235
|
+
readonly environment: string | null;
|
|
4236
|
+
readonly parameters: Readonly<Record<string, string>>;
|
|
4237
|
+
readonly labels: Readonly<Record<string, string>>;
|
|
4238
|
+
readonly sha: string | null;
|
|
4239
|
+
};
|
|
4240
|
+
```
|
|
4241
|
+
|
|
4242
|
+
One observation of a latency. Time units are converted to milliseconds.
|
|
4243
|
+
|
|
4244
|
+
#### `latencyObservations`
|
|
4245
|
+
|
|
4246
|
+
```ts
|
|
4247
|
+
export function latencyObservations(entries: readonly LatencyEntry[]): LatencyObservation[]
|
|
4248
|
+
```
|
|
4249
|
+
|
|
4250
|
+
The observations in recorded results, in the order given. Entries with no latency contribute none.
|
|
4251
|
+
|
|
4252
|
+
#### `LatencyGrouping`
|
|
4253
|
+
|
|
4254
|
+
```ts
|
|
4255
|
+
export type LatencyGrouping = 'none' | 'device' | 'build' | 'window';
|
|
4256
|
+
```
|
|
4257
|
+
|
|
4258
|
+
How observations are grouped: not at all, by `device`, by `build`, or by the UTC `window` (day) they were recorded in.
|
|
4259
|
+
|
|
4260
|
+
#### `LATENCY_GROUPINGS`
|
|
4261
|
+
|
|
4262
|
+
```ts
|
|
4263
|
+
export const LATENCY_GROUPINGS: readonly LatencyGrouping[]
|
|
4264
|
+
```
|
|
4265
|
+
|
|
4266
|
+
The groupings, in the order a control offers them.
|
|
4267
|
+
|
|
4268
|
+
#### `groupOf`
|
|
4269
|
+
|
|
4270
|
+
```ts
|
|
4271
|
+
export function groupOf(observation: LatencyObservation, by: LatencyGrouping): string
|
|
4272
|
+
```
|
|
4273
|
+
|
|
4274
|
+
The group an observation falls in. `device` is the measurement's cohort, else the result's label, parameter or
|
|
4275
|
+
environment; `build` is the cohort or label, else the commit. One that names nothing is `unknown device` or
|
|
4276
|
+
`unknown build`: it is never merged into a named group.
|
|
4277
|
+
|
|
4278
|
+
#### `LatencyMetric`
|
|
4279
|
+
|
|
4280
|
+
```ts
|
|
4281
|
+
export type LatencyMetric = {
|
|
4282
|
+
/** Equal for observations of the same name, unit and boundaries. */
|
|
4283
|
+
readonly key: string;
|
|
4284
|
+
readonly name: string;
|
|
4285
|
+
readonly unit: string;
|
|
4286
|
+
readonly boundary: Boundary;
|
|
4287
|
+
};
|
|
4288
|
+
```
|
|
4289
|
+
|
|
4290
|
+
A measured interval: the name, the unit and the ends. Observations of one metric are comparable; of two metrics they are not.
|
|
4291
|
+
|
|
4292
|
+
#### `LatencySummary`
|
|
4293
|
+
|
|
4294
|
+
```ts
|
|
4295
|
+
export type LatencySummary = {
|
|
4296
|
+
readonly metric: LatencyMetric;
|
|
4297
|
+
/** The group (a device, a build, a day, or all). */
|
|
4298
|
+
readonly group: string;
|
|
4299
|
+
/** Every observation, including those that timed out or failed. */
|
|
4300
|
+
readonly samples: number;
|
|
4301
|
+
/** Observations that completed. */
|
|
4302
|
+
readonly observed: number;
|
|
4303
|
+
readonly timeouts: number;
|
|
4304
|
+
readonly failures: number;
|
|
4305
|
+
/** Observations over their budget, plus every timeout or failure when a budget is set. */
|
|
4306
|
+
readonly breaches: number;
|
|
4307
|
+
/** The budget, from the newest observation that has one. */
|
|
4308
|
+
readonly budget?: number;
|
|
4309
|
+
/** The median, or undefined with the reason in `withheld`. */
|
|
4310
|
+
readonly p50?: number;
|
|
4311
|
+
readonly p95?: number;
|
|
4312
|
+
/** Why a figure is not shown, in words. */
|
|
4313
|
+
readonly withheld: readonly string[];
|
|
4314
|
+
/** True when a timeout or failure's waiting time is in the figures: they are lower bounds. */
|
|
4315
|
+
readonly lowerBound: boolean;
|
|
4316
|
+
readonly max: number;
|
|
4317
|
+
/** The newest observation's value. */
|
|
4318
|
+
readonly last: number;
|
|
4319
|
+
readonly lastAt: number;
|
|
4320
|
+
/** Whether the numbers support a statement about the user's wait: false when the boundaries are not declared. */
|
|
4321
|
+
readonly claim: boolean;
|
|
4322
|
+
/** What the reader must not assume, in words: partial interval, missing boundaries, a single observation. */
|
|
4323
|
+
readonly gaps: readonly string[];
|
|
4324
|
+
readonly instrumentation: readonly string[];
|
|
4325
|
+
readonly environments: readonly string[];
|
|
4326
|
+
/** Other facts the observations differ by: cohort entries and parameters, as `key=value`. */
|
|
4327
|
+
readonly conditions: readonly string[];
|
|
4328
|
+
readonly budgets: readonly number[];
|
|
4329
|
+
};
|
|
4330
|
+
```
|
|
4331
|
+
|
|
4332
|
+
What the observations of one metric in one group show, and what they cannot.
|
|
4333
|
+
|
|
4334
|
+
#### `LatencyReview`
|
|
4335
|
+
|
|
4336
|
+
```ts
|
|
4337
|
+
export type LatencyReview = {
|
|
4338
|
+
readonly by: LatencyGrouping;
|
|
4339
|
+
readonly metrics: readonly { readonly metric: LatencyMetric; readonly groups: readonly LatencySummary[] }[];
|
|
4340
|
+
/** Results that recorded no product latency. They are unknown, not fast. */
|
|
4341
|
+
readonly unmeasured: number;
|
|
4342
|
+
/** Results that did. */
|
|
4343
|
+
readonly measured: number;
|
|
4344
|
+
};
|
|
4345
|
+
```
|
|
4346
|
+
|
|
4347
|
+
The metrics of a result set, each with its summary in every group, groups newest first.
|
|
4348
|
+
|
|
4349
|
+
#### `latencyReview`
|
|
4350
|
+
|
|
4351
|
+
```ts
|
|
4352
|
+
export function latencyReview(entries: readonly LatencyEntry[], by: LatencyGrouping = 'none'): LatencyReview
|
|
4353
|
+
```
|
|
4354
|
+
|
|
4355
|
+
Reads recorded results (any order) into a review: for every metric, a summary in every group. A result with no
|
|
4356
|
+
product latency counts only in `unmeasured`. Metrics are listed by name; groups newest first.
|
|
4357
|
+
|
|
4358
|
+
- `by`: How observations are grouped. Default `none`.
|
|
4359
|
+
|
|
4360
|
+
**Example**
|
|
4361
|
+
|
|
4362
|
+
```ts
|
|
4363
|
+
const review = latencyReview(history.history, 'build');
|
|
4364
|
+
review.metrics[0]?.groups[0]?.p95; // undefined until 20 observations of that build
|
|
4365
|
+
```
|
|
4366
|
+
|
|
4367
|
+
#### `LatencyDelta`
|
|
4368
|
+
|
|
4369
|
+
```ts
|
|
4370
|
+
export type LatencyDelta = {
|
|
4371
|
+
readonly baseline: number;
|
|
4372
|
+
readonly candidate: number;
|
|
4373
|
+
/** Candidate minus baseline, in the metric's unit. Positive is slower. */
|
|
4374
|
+
readonly absolute: number;
|
|
4375
|
+
/** The absolute change over the baseline, or null when the baseline is zero. */
|
|
4376
|
+
readonly relative: number | null;
|
|
4377
|
+
};
|
|
4378
|
+
```
|
|
4379
|
+
|
|
4380
|
+
A change of one figure from the baseline to the candidate.
|
|
4381
|
+
|
|
4382
|
+
#### `LatencyChange`
|
|
4383
|
+
|
|
4384
|
+
```ts
|
|
4385
|
+
export type LatencyChange = {
|
|
4386
|
+
/** Whether a change is stated at all. */
|
|
4387
|
+
readonly comparable: boolean;
|
|
4388
|
+
/** `p50` or `p95` when both sides support it; `last` compares the newest observation of each, which is not a percentile. */
|
|
4389
|
+
readonly basis?: 'p50' | 'p95' | 'last';
|
|
4390
|
+
readonly p50?: LatencyDelta;
|
|
4391
|
+
readonly p95?: LatencyDelta;
|
|
4392
|
+
readonly last?: LatencyDelta;
|
|
4393
|
+
/** Why no change is stated. */
|
|
4394
|
+
readonly reasons: readonly string[];
|
|
4395
|
+
/** Differences in how the two sides were measured or run. A change should be read with them. */
|
|
4396
|
+
readonly disclosures: readonly string[];
|
|
4397
|
+
};
|
|
4398
|
+
```
|
|
4399
|
+
|
|
4400
|
+
What changed between two summaries, or why that cannot be said.
|
|
4401
|
+
|
|
4402
|
+
#### `compareLatency`
|
|
4403
|
+
|
|
4404
|
+
```ts
|
|
4405
|
+
export function compareLatency(baseline: LatencySummary, candidate: LatencySummary): LatencyChange
|
|
4406
|
+
```
|
|
4407
|
+
|
|
4408
|
+
The change from a baseline to a candidate of the same metric. Different metrics (names, units or boundaries) are not
|
|
4409
|
+
compared and the reason is given. When both sides support p50 (and p95) the change is of those; otherwise only the
|
|
4410
|
+
newest observation of each is compared, labelled as such. Changes of instrumentation, environments, other
|
|
4411
|
+
conditions, budget and partial or lower-bound figures are listed in `disclosures`, never hidden.
|
|
4412
|
+
|
|
4413
|
+
#### `formatLatency`
|
|
4414
|
+
|
|
4415
|
+
```ts
|
|
4416
|
+
export function formatLatency(value: number | undefined, unit: string): string
|
|
4417
|
+
```
|
|
4418
|
+
|
|
4419
|
+
`500 ms`, `1.2 s`: a latency in the largest unit that keeps it readable. Non-time units print as given.
|
|
4420
|
+
|
|
4076
4421
|
### Environment axis of a layer
|
|
4077
4422
|
|
|
4078
4423
|
Source: `packages/reports/src/layer-axes.ts`. Import: `@crvouga/atlas/reports/layer-axes`
|
|
@@ -8318,6 +8663,8 @@ export type TestResultEntry = {
|
|
|
8318
8663
|
readonly detail: string;
|
|
8319
8664
|
/** What it carried beyond its outcome (attachments, steps, retries, tags, labels), when anything. */
|
|
8320
8665
|
readonly evidence?: EvidenceSummary;
|
|
8666
|
+
/** Its product-latency measurements (`kind: product-latency`), when it recorded any. Separate from `durationMs`, which is how long the test took (setup, waits and pacing included). */
|
|
8667
|
+
readonly latency?: readonly EvidenceMeasurement[];
|
|
8321
8668
|
};
|
|
8322
8669
|
```
|
|
8323
8670
|
|
package/docs/reference/schema.md
CHANGED
|
@@ -742,6 +742,22 @@ export type LayerRun = z.output<typeof LayerRunSchema>;
|
|
|
742
742
|
|
|
743
743
|
The execution of a layer, parsed from `LayerRunSchema`.
|
|
744
744
|
|
|
745
|
+
#### `PRODUCT_LATENCY_KIND`
|
|
746
|
+
|
|
747
|
+
```ts
|
|
748
|
+
export const PRODUCT_LATENCY_KIND = …
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
The `kind` of a measurement that is a named, user-visible product latency (see docs/rich-evidence.md#product-latency).
|
|
752
|
+
|
|
753
|
+
#### `MeasurementBoundarySchema`
|
|
754
|
+
|
|
755
|
+
```ts
|
|
756
|
+
export const MeasurementBoundarySchema = …
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
The two ends of a measured interval, named in the producer's words: `{ from: 'submit pressed', to: 'confirmation visible' }`.
|
|
760
|
+
|
|
745
761
|
#### `MeasurementSchema`
|
|
746
762
|
|
|
747
763
|
```ts
|
|
@@ -751,6 +767,18 @@ export const MeasurementSchema = …
|
|
|
751
767
|
A number a check measured, such as a latency or a size, with its unit and an optional allowance. A measurement over
|
|
752
768
|
its `limit` is reported and never changes the check's status.
|
|
753
769
|
|
|
770
|
+
A measurement of `kind` `product-latency` is how quickly the product responded to a user, between two named events.
|
|
771
|
+
It is not the check's duration (which includes setup, waits and pacing) and a number with no `kind` is never read as
|
|
772
|
+
product latency. All the fields after `limit` are optional and describe the measurement; none decides a status.
|
|
773
|
+
|
|
774
|
+
#### `Measurement`
|
|
775
|
+
|
|
776
|
+
```ts
|
|
777
|
+
export type Measurement = z.output<typeof MeasurementSchema>;
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
A measurement as stored in a layer, parsed from `MeasurementSchema`.
|
|
781
|
+
|
|
754
782
|
#### `ExtensionsSchema`
|
|
755
783
|
|
|
756
784
|
```ts
|
package/docs/rich-evidence.md
CHANGED
|
@@ -109,6 +109,49 @@ Every field is optional, and every value except the booleans is an open string.
|
|
|
109
109
|
- **It decides nothing.** A scope never changes a status, a slot or a winner. A scope an older Atlas cannot read is
|
|
110
110
|
dropped, and the attachment, check and layer stay.
|
|
111
111
|
|
|
112
|
+
## Product latency
|
|
113
|
+
|
|
114
|
+
A test's `durationMs` includes fixture setup, deliberate waits, demo pacing, retries and cleanup, so it says how long the
|
|
115
|
+
test ran and nothing about how quickly the product responded. Name the numbers that do, as measurements of `kind`
|
|
116
|
+
`product-latency` between two events:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
measurements: [
|
|
120
|
+
{ name: 'Fixture setup', value: 20_000, unit: 'ms' }, // just a number: never read as latency
|
|
121
|
+
{
|
|
122
|
+
name: 'Confirmation', value: 500, unit: 'ms', kind: 'product-latency',
|
|
123
|
+
from: 'submit pressed', to: 'confirmation visible', // the boundaries are part of the claim
|
|
124
|
+
limit: 800, // the budget
|
|
125
|
+
source: 'app-instrumentation', instrumentation: 'spans-v2',
|
|
126
|
+
labels: { device: 'pixel-8', build: '4821' } // what it is comparable with
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The test page shows **Product latency** apart from the test statistics, whose durations are labelled as test execution
|
|
132
|
+
time. Every field after `unit` is optional and none of them decides a status, a slot or a winner.
|
|
133
|
+
|
|
134
|
+
- **Only named measurements are latency.** A check's duration, a video's length, a step and a number with no `kind` are
|
|
135
|
+
never product latency. A test that recorded none shows that its latency is unknown, not fast.
|
|
136
|
+
- **Boundaries are part of the claim.** Without both `from` and `to` the numbers are shown but no percentile or product
|
|
137
|
+
claim is made, and the page says so. When the interval you measured is only part of what the name claims, say what it
|
|
138
|
+
claims in `intended` (`{ "to": "audible response" }` on a measurement that ends at `tts dispatch`) and the viewer labels
|
|
139
|
+
it a partial interval, never the complete one.
|
|
140
|
+
- **Percentiles need samples.** p50 needs 5 observations and p95 needs 20; fewer shows `Withheld` with the count it has.
|
|
141
|
+
One observation shows no spread or confidence.
|
|
142
|
+
- **A timeout is a sample.** `outcome: "timeout"` or `"failed"` (anything but `observed`) means `value` is how long the
|
|
143
|
+
observation waited. It stays in the sample count and the percentiles, counts as over budget whenever a `limit` is set,
|
|
144
|
+
and marks the figures as lower bounds, so a stuck product never looks healthy.
|
|
145
|
+
- **Groups.** The viewer groups observations by `device`, `build` or the UTC day they were recorded, taking `device` and
|
|
146
|
+
`build` from the measurement's `labels`, else the result's labels, parameters, environment and commit. Each group has
|
|
147
|
+
its own sample count, p50/p95, budget and breaches. A group that names nothing is `unknown device` or `unknown build`.
|
|
148
|
+
- **Baselines.** A group is compared with a chosen baseline by absolute and relative change of p50 (and p95), or of the
|
|
149
|
+
newest observation when percentiles are not supported on both sides. Measurements with different names, units or
|
|
150
|
+
boundaries are not compared. A change of `instrumentation`, environment, other labels or parameters, or budget between
|
|
151
|
+
the two sides is listed beside the change.
|
|
152
|
+
- **Reading it from the API.** `GET /api/reports/tests/history` entries carry `latency` (their product-latency
|
|
153
|
+
measurements) next to `durationMs`. The pure functions are in `@crvouga/atlas/reports/latency`.
|
|
154
|
+
|
|
112
155
|
## Finding runs and artifacts
|
|
113
156
|
|
|
114
157
|
The viewer's **Runs** and **Artifacts** tabs and the API answer one query, so a link means the same thing everywhere.
|
package/docs/ui.md
CHANGED
|
@@ -87,6 +87,29 @@ when Watch is on screen.
|
|
|
87
87
|
- The spec index searches titles, paths and declared domains, and can group by the domain from spec metadata (specs with
|
|
88
88
|
none are listed under "No domain declared"; the Tests view reports domain problems).
|
|
89
89
|
|
|
90
|
+
## Product latency
|
|
91
|
+
|
|
92
|
+
A test's duration and its product latency are different things and the viewer never mixes them (`explorer/ProductLatency.tsx`,
|
|
93
|
+
`@crvouga/atlas/reports/latency`). Statistics and the trend are labelled test execution time. Product latency is read only from
|
|
94
|
+
measurements named `product-latency`; it shows boundaries, group (device, build, day), samples, budget and breaches, and a
|
|
95
|
+
baseline comparison that lists changes of instrumentation or conditions. Percentiles and product claims that the evidence
|
|
96
|
+
cannot support are shown as `Withheld` with the reason, a partial interval is badged, timeouts count as samples and breaches,
|
|
97
|
+
and a test with no latency says it is unknown, not fast.
|
|
98
|
+
|
|
99
|
+
## Outcome summary
|
|
100
|
+
|
|
101
|
+
A test or behavior detail states what the **selected** result says apart from what the other current sources say
|
|
102
|
+
(`evidence/outcome.ts`, `outcome-summary.tsx`). The heading is the selected source's outcome first ("Selected result
|
|
103
|
+
passed; another scoped source failed"), never the combined status; the combined status is labelled as the worst current
|
|
104
|
+
result. Each other current source is one click away, and sources that share an environment are told apart by status,
|
|
105
|
+
time, revision and their place among them. A failure leads with whether the behavior was reached (decided only by the
|
|
106
|
+
recorded step `kind`: a `hook`, `setup`, `fixture`, `before*` or `given` step failing is "Behavior not verified: setup
|
|
107
|
+
failed", `when`, `then`, `assertion` or `expect` is "Expectation not met", anything else is unknown), then the expected
|
|
108
|
+
and observed values and the closest step and video, then the original output. The cause (product, test harness,
|
|
109
|
+
environment) is unknown unless the producer declared it under the `atlas.failure` extension; it is never read from error
|
|
110
|
+
wording. Records of one source whose outcome changed are listed oldest first, and a later pass is never called a fix.
|
|
111
|
+
In the explorer the chosen source is the `source` search parameter (a layer id; unlike `layer` it does not pin a run).
|
|
112
|
+
|
|
90
113
|
## Icons and notation marks
|
|
91
114
|
|
|
92
115
|
Use named imports from `@tabler/icons-react` for all UI glyphs. The icon package supplies SVG paths;
|
|
@@ -156,7 +179,7 @@ The narrowly scoped exceptions are:
|
|
|
156
179
|
| --- | --- | --- |
|
|
157
180
|
| Shared shell | Project switching, spec navigation, run selection, connection status, diagnostics, mobile index | AppShell, Burger, NavLink, Menu, Popover, NativeSelect, Drawer |
|
|
158
181
|
| Project home | Tests/runs/artifacts tabs, a run's Artifacts (files of its layers, shared recordings, direct links), metrics, search, status/environment/domain/artifact-tag filters, run and artifact facets with saved views, grouping, sorting, show more | Container, Tabs, SimpleGrid, Paper, TextInput, MultiSelect, Popover, Button, Accordion |
|
|
159
|
-
| Test detail | Current outcomes, statistics, duration graph, history, artifacts, recording; the `layer` address parameter pins it to one run's result, labelled "This run" against "Latest combined evidence" | Drawer, Table, Badge, Alert, Stage |
|
|
182
|
+
| Test detail | Current outcomes, statistics, duration graph, product latency with budgets and baselines, history, artifacts, recording; the `layer` address parameter pins it to one run's result, labelled "This run" against "Latest combined evidence" | Drawer, Table, Badge, Alert, Stage |
|
|
160
183
|
| Specs | All six existing kinds, overview/index with title search and folder or declared-domain grouping, Gherkin rules/scenarios/examples with a coverage summary and Read / Watch / Inspect per behavior, Quint declarations/traces, DMN tables, source text | SpecHeader, Accordion, Tabs, Table, CodeHighlightTabs, ResultRow |
|
|
161
184
|
| Statecharts | Graph, screen/event/journey details, developer metadata, chart test results | Mantine cards/navigation and product compositions; graph exception above |
|
|
162
185
|
| Evidence | Steps, retries, annotations, facts, unknown extensions, safe downloads and previews | List, Badge, Accordion, Code, Image, native media |
|
package/llms-full.txt
CHANGED
|
@@ -1096,6 +1096,49 @@ Every field is optional, and every value except the booleans is an open string.
|
|
|
1096
1096
|
- **It decides nothing.** A scope never changes a status, a slot or a winner. A scope an older Atlas cannot read is
|
|
1097
1097
|
dropped, and the attachment, check and layer stay.
|
|
1098
1098
|
|
|
1099
|
+
## Product latency
|
|
1100
|
+
|
|
1101
|
+
A test's `durationMs` includes fixture setup, deliberate waits, demo pacing, retries and cleanup, so it says how long the
|
|
1102
|
+
test ran and nothing about how quickly the product responded. Name the numbers that do, as measurements of `kind`
|
|
1103
|
+
`product-latency` between two events:
|
|
1104
|
+
|
|
1105
|
+
```ts
|
|
1106
|
+
measurements: [
|
|
1107
|
+
{ name: 'Fixture setup', value: 20_000, unit: 'ms' }, // just a number: never read as latency
|
|
1108
|
+
{
|
|
1109
|
+
name: 'Confirmation', value: 500, unit: 'ms', kind: 'product-latency',
|
|
1110
|
+
from: 'submit pressed', to: 'confirmation visible', // the boundaries are part of the claim
|
|
1111
|
+
limit: 800, // the budget
|
|
1112
|
+
source: 'app-instrumentation', instrumentation: 'spans-v2',
|
|
1113
|
+
labels: { device: 'pixel-8', build: '4821' } // what it is comparable with
|
|
1114
|
+
}
|
|
1115
|
+
]
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
The test page shows **Product latency** apart from the test statistics, whose durations are labelled as test execution
|
|
1119
|
+
time. Every field after `unit` is optional and none of them decides a status, a slot or a winner.
|
|
1120
|
+
|
|
1121
|
+
- **Only named measurements are latency.** A check's duration, a video's length, a step and a number with no `kind` are
|
|
1122
|
+
never product latency. A test that recorded none shows that its latency is unknown, not fast.
|
|
1123
|
+
- **Boundaries are part of the claim.** Without both `from` and `to` the numbers are shown but no percentile or product
|
|
1124
|
+
claim is made, and the page says so. When the interval you measured is only part of what the name claims, say what it
|
|
1125
|
+
claims in `intended` (`{ "to": "audible response" }` on a measurement that ends at `tts dispatch`) and the viewer labels
|
|
1126
|
+
it a partial interval, never the complete one.
|
|
1127
|
+
- **Percentiles need samples.** p50 needs 5 observations and p95 needs 20; fewer shows `Withheld` with the count it has.
|
|
1128
|
+
One observation shows no spread or confidence.
|
|
1129
|
+
- **A timeout is a sample.** `outcome: "timeout"` or `"failed"` (anything but `observed`) means `value` is how long the
|
|
1130
|
+
observation waited. It stays in the sample count and the percentiles, counts as over budget whenever a `limit` is set,
|
|
1131
|
+
and marks the figures as lower bounds, so a stuck product never looks healthy.
|
|
1132
|
+
- **Groups.** The viewer groups observations by `device`, `build` or the UTC day they were recorded, taking `device` and
|
|
1133
|
+
`build` from the measurement's `labels`, else the result's labels, parameters, environment and commit. Each group has
|
|
1134
|
+
its own sample count, p50/p95, budget and breaches. A group that names nothing is `unknown device` or `unknown build`.
|
|
1135
|
+
- **Baselines.** A group is compared with a chosen baseline by absolute and relative change of p50 (and p95), or of the
|
|
1136
|
+
newest observation when percentiles are not supported on both sides. Measurements with different names, units or
|
|
1137
|
+
boundaries are not compared. A change of `instrumentation`, environment, other labels or parameters, or budget between
|
|
1138
|
+
the two sides is listed beside the change.
|
|
1139
|
+
- **Reading it from the API.** `GET /api/reports/tests/history` entries carry `latency` (their product-latency
|
|
1140
|
+
measurements) next to `durationMs`. The pure functions are in `@crvouga/atlas/reports/latency`.
|
|
1141
|
+
|
|
1099
1142
|
## Finding runs and artifacts
|
|
1100
1143
|
|
|
1101
1144
|
The viewer's **Runs** and **Artifacts** tabs and the API answer one query, so a link means the same thing everywhere.
|
|
@@ -2830,7 +2873,7 @@ carry any of these. Every one is optional.
|
|
|
2830
2873
|
| `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). |
|
|
2831
2874
|
| `links[]` | Where to read more: the CI run, the issue, the trace client. |
|
|
2832
2875
|
| `extensions{}` | Structured data of your own or a tool's, under a name you own. |
|
|
2833
|
-
| `measurements[]` | Numbers, with units and an allowance (already part of the contract). |
|
|
2876
|
+
| `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). |
|
|
2834
2877
|
|
|
2835
2878
|
An attachment may carry its own `tags[]` (`demo`): shown as `#demo` and searchable; see
|
|
2836
2879
|
[Tagging artifacts and demos](rich-evidence.md#tagging-artifacts-and-demos).
|
|
@@ -2900,6 +2943,23 @@ every kind of evidence and values from the future) and `layer-extensibility.test
|
|
|
2900
2943
|
fields survive at every level, malformed evidence is dropped without losing the result, and a reader of the
|
|
2901
2944
|
first version of the format still reads it all.
|
|
2902
2945
|
|
|
2946
|
+
### Saying what failed
|
|
2947
|
+
|
|
2948
|
+
A failed check or step may declare the expectation it broke under the `atlas.failure` extension, so a reviewer sees
|
|
2949
|
+
it before the raw log. Every field is optional, and the declaration never changes the result.
|
|
2950
|
+
|
|
2951
|
+
```json
|
|
2952
|
+
{
|
|
2953
|
+
"name": "The list includes tea", "kind": "then", "status": "failed",
|
|
2954
|
+
"extensions": { "atlas.failure": { "expected": "tea in the list", "actual": "the list is empty", "attribution": "product" } }
|
|
2955
|
+
}
|
|
2956
|
+
```
|
|
2957
|
+
|
|
2958
|
+
`expected` and `actual` are shown as written. `attribution` is `product`, `test-harness` or `environment`; leave it out
|
|
2959
|
+
when you do not know, and the viewer says the cause is unknown. It is never inferred from the message. Whether setup
|
|
2960
|
+
failed before the behavior started comes from the failed step's `kind`: `hook`, `setup`, `fixture`, `before`,
|
|
2961
|
+
`beforeAll`, `beforeEach` or `given` mean preparation; `when`, `then`, `assertion` or `expect` mean the behavior.
|
|
2962
|
+
|
|
2903
2963
|
## Attach evidence
|
|
2904
2964
|
|
|
2905
2965
|
### From the command line
|
|
@@ -3919,6 +3979,29 @@ when Watch is on screen.
|
|
|
3919
3979
|
- The spec index searches titles, paths and declared domains, and can group by the domain from spec metadata (specs with
|
|
3920
3980
|
none are listed under "No domain declared"; the Tests view reports domain problems).
|
|
3921
3981
|
|
|
3982
|
+
## Product latency
|
|
3983
|
+
|
|
3984
|
+
A test's duration and its product latency are different things and the viewer never mixes them (`explorer/ProductLatency.tsx`,
|
|
3985
|
+
`@crvouga/atlas/reports/latency`). Statistics and the trend are labelled test execution time. Product latency is read only from
|
|
3986
|
+
measurements named `product-latency`; it shows boundaries, group (device, build, day), samples, budget and breaches, and a
|
|
3987
|
+
baseline comparison that lists changes of instrumentation or conditions. Percentiles and product claims that the evidence
|
|
3988
|
+
cannot support are shown as `Withheld` with the reason, a partial interval is badged, timeouts count as samples and breaches,
|
|
3989
|
+
and a test with no latency says it is unknown, not fast.
|
|
3990
|
+
|
|
3991
|
+
## Outcome summary
|
|
3992
|
+
|
|
3993
|
+
A test or behavior detail states what the **selected** result says apart from what the other current sources say
|
|
3994
|
+
(`evidence/outcome.ts`, `outcome-summary.tsx`). The heading is the selected source's outcome first ("Selected result
|
|
3995
|
+
passed; another scoped source failed"), never the combined status; the combined status is labelled as the worst current
|
|
3996
|
+
result. Each other current source is one click away, and sources that share an environment are told apart by status,
|
|
3997
|
+
time, revision and their place among them. A failure leads with whether the behavior was reached (decided only by the
|
|
3998
|
+
recorded step `kind`: a `hook`, `setup`, `fixture`, `before*` or `given` step failing is "Behavior not verified: setup
|
|
3999
|
+
failed", `when`, `then`, `assertion` or `expect` is "Expectation not met", anything else is unknown), then the expected
|
|
4000
|
+
and observed values and the closest step and video, then the original output. The cause (product, test harness,
|
|
4001
|
+
environment) is unknown unless the producer declared it under the `atlas.failure` extension; it is never read from error
|
|
4002
|
+
wording. Records of one source whose outcome changed are listed oldest first, and a later pass is never called a fix.
|
|
4003
|
+
In the explorer the chosen source is the `source` search parameter (a layer id; unlike `layer` it does not pin a run).
|
|
4004
|
+
|
|
3922
4005
|
## Icons and notation marks
|
|
3923
4006
|
|
|
3924
4007
|
Use named imports from `@tabler/icons-react` for all UI glyphs. The icon package supplies SVG paths;
|
|
@@ -3988,7 +4071,7 @@ The narrowly scoped exceptions are:
|
|
|
3988
4071
|
| --- | --- | --- |
|
|
3989
4072
|
| Shared shell | Project switching, spec navigation, run selection, connection status, diagnostics, mobile index | AppShell, Burger, NavLink, Menu, Popover, NativeSelect, Drawer |
|
|
3990
4073
|
| Project home | Tests/runs/artifacts tabs, a run's Artifacts (files of its layers, shared recordings, direct links), metrics, search, status/environment/domain/artifact-tag filters, run and artifact facets with saved views, grouping, sorting, show more | Container, Tabs, SimpleGrid, Paper, TextInput, MultiSelect, Popover, Button, Accordion |
|
|
3991
|
-
| Test detail | Current outcomes, statistics, duration graph, history, artifacts, recording; the `layer` address parameter pins it to one run's result, labelled "This run" against "Latest combined evidence" | Drawer, Table, Badge, Alert, Stage |
|
|
4074
|
+
| Test detail | Current outcomes, statistics, duration graph, product latency with budgets and baselines, history, artifacts, recording; the `layer` address parameter pins it to one run's result, labelled "This run" against "Latest combined evidence" | Drawer, Table, Badge, Alert, Stage |
|
|
3992
4075
|
| Specs | All six existing kinds, overview/index with title search and folder or declared-domain grouping, Gherkin rules/scenarios/examples with a coverage summary and Read / Watch / Inspect per behavior, Quint declarations/traces, DMN tables, source text | SpecHeader, Accordion, Tabs, Table, CodeHighlightTabs, ResultRow |
|
|
3993
4076
|
| Statecharts | Graph, screen/event/journey details, developer metadata, chart test results | Mantine cards/navigation and product compositions; graph exception above |
|
|
3994
4077
|
| Evidence | Steps, retries, annotations, facts, unknown extensions, safe downloads and previews | List, Badge, Accordion, Code, Image, native media |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crvouga/atlas",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Atlas: specs of any kind, reported as layered evidence. Publish checks from any runner (JUnit, CTRF, a script), combine them across nodes, branches, environments and backends, gate CI on the result, and run statecharts against the real app.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|