@crvouga/atlas 1.7.0 → 1.9.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 (46) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/dist/reports/src/evidence-detail.js +6 -1
  3. package/dist/reports/src/evidence-types.d.ts +2 -0
  4. package/dist/reports/src/explorer-types.d.ts +19 -0
  5. package/dist/reports/src/latency.d.ts +204 -0
  6. package/dist/reports/src/latency.js +219 -0
  7. package/dist/reports/src/reliability.d.ts +181 -0
  8. package/dist/reports/src/reliability.js +294 -0
  9. package/dist/reports/src/report-index.js +1 -1
  10. package/dist/reports/src/reports-explorer-route.d.ts +19 -6
  11. package/dist/reports/src/reports-route.d.ts +19 -6
  12. package/dist/reports/src/test-catalog.d.ts +11 -3
  13. package/dist/reports/src/test-catalog.js +12 -5
  14. package/dist/schema/src/api.d.ts +377 -0
  15. package/dist/schema/src/api.js +55 -3
  16. package/dist/schema/src/layer.d.ts +90 -0
  17. package/dist/schema/src/layer.js +20 -1
  18. package/dist/server/src/app.js +15 -0
  19. package/dist/server/src/llms.js +1 -0
  20. package/dist/server/src/openapi.js +25 -2
  21. package/docs/extending.md +1 -1
  22. package/docs/reference/http-api.md +48 -0
  23. package/docs/reference/reports.md +619 -1
  24. package/docs/reference/schema.md +39 -0
  25. package/docs/rich-evidence.md +43 -0
  26. package/docs/server.md +18 -0
  27. package/docs/ui.md +20 -1
  28. package/llms-full.txt +130 -2
  29. package/package.json +1 -1
  30. package/packages/schema/schemas/check-detail.schema.json +56 -1
  31. package/packages/schema/schemas/effective-delta.schema.json +86 -2
  32. package/packages/schema/schemas/effective-evidence.schema.json +86 -2
  33. package/packages/schema/schemas/evidence-page.schema.json +13 -0
  34. package/packages/schema/schemas/layer-rows.schema.json +73 -2
  35. package/packages/schema/schemas/layer.schema.json +112 -2
  36. package/packages/schema/schemas/publish-checks.schema.json +56 -1
  37. package/packages/schema/schemas/test-catalog.schema.json +102 -0
  38. package/packages/schema/schemas/test-history.schema.json +204 -0
  39. package/packages/schema/schemas/test-reliability.schema.json +629 -0
  40. package/packages/server/openapi.json +2354 -661
  41. package/packages/visualizer/src/evidence/check-detail.tsx +9 -0
  42. package/packages/visualizer/src/explorer/ProductLatency.tsx +214 -0
  43. package/packages/visualizer/src/explorer/Reliability.tsx +337 -0
  44. package/packages/visualizer/src/explorer/TestDetail.tsx +12 -2
  45. package/packages/visualizer/src/explorer/catalog.tsx +1 -1
  46. package/packages/visualizer/src/explorer/search.ts +36 -0
@@ -300,6 +300,17 @@ export const TestHistorySchema = …
300
300
 
301
301
  `GET /api/reports/tests/history`: one test and every result recorded for it (at most 100), newest first.
302
302
 
303
+ #### `TestReliabilitySchema`
304
+
305
+ ```ts
306
+ export const TestReliabilitySchema = …
307
+ ```
308
+
309
+ `GET /api/reports/tests/reliability`: how reliable one test is inside a scope, and exactly how that figure was counted.
310
+ The snapshots one run recorded for one producer, environment and parameter set are one independent execution; only
311
+ `passed` and `failed` executions are trials; `notes` say when the sample is too small, pooled, selectively rerun or
312
+ bounded. It reads results and never changes a status. It is not a production failure probability.
313
+
303
314
  #### `ReportRunSchema`
304
315
 
305
316
  ```ts
@@ -742,6 +753,22 @@ export type LayerRun = z.output<typeof LayerRunSchema>;
742
753
 
743
754
  The execution of a layer, parsed from `LayerRunSchema`.
744
755
 
756
+ #### `PRODUCT_LATENCY_KIND`
757
+
758
+ ```ts
759
+ export const PRODUCT_LATENCY_KIND = …
760
+ ```
761
+
762
+ The `kind` of a measurement that is a named, user-visible product latency (see docs/rich-evidence.md#product-latency).
763
+
764
+ #### `MeasurementBoundarySchema`
765
+
766
+ ```ts
767
+ export const MeasurementBoundarySchema = …
768
+ ```
769
+
770
+ The two ends of a measured interval, named in the producer's words: `{ from: 'submit pressed', to: 'confirmation visible' }`.
771
+
745
772
  #### `MeasurementSchema`
746
773
 
747
774
  ```ts
@@ -751,6 +778,18 @@ export const MeasurementSchema = …
751
778
  A number a check measured, such as a latency or a size, with its unit and an optional allowance. A measurement over
752
779
  its `limit` is reported and never changes the check's status.
753
780
 
781
+ A measurement of `kind` `product-latency` is how quickly the product responded to a user, between two named events.
782
+ It is not the check's duration (which includes setup, waits and pacing) and a number with no `kind` is never read as
783
+ product latency. All the fields after `limit` are optional and describe the measurement; none decides a status.
784
+
785
+ #### `Measurement`
786
+
787
+ ```ts
788
+ export type Measurement = z.output<typeof MeasurementSchema>;
789
+ ```
790
+
791
+ A measurement as stored in a layer, parsed from `MeasurementSchema`.
792
+
754
793
  #### `ExtensionsSchema`
755
794
 
756
795
  ```ts
@@ -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/server.md CHANGED
@@ -374,6 +374,7 @@ with `format=markdown` (or `Accept: text/markdown`), and takes the project prefi
374
374
  | Every test, a summary (by status, environment, producer, domain), insights (failing, flaky, slowest) and facets | `GET /api/reports/tests` |
375
375
  | Only some: `q` (all words), `status` (or `flaky`), `environment`, `producer`, `domain`, `spec`, `tag`, `run`; `sort`, `offset`, `limit` | `GET /api/reports/tests?status=failed&environment=ios` |
376
376
  | One test: every recorded result (status, duration, environment, branch, commit, CI run), newest first | `GET /api/reports/tests/history?key=<key>` |
377
+ | How reliable one test is in a scope (environment, producer, parameters, branch, commit, spec revision, time window): independent executions, first-try and eventual success, what was left out and why | `GET /api/reports/tests/reliability?key=<key>&environment=phone` |
377
378
  | Why Specs, domains and counts disagree: ambiguous spec paths, shared domains, gaps, a reconciliation of the totals | `GET /api/reports/tests/health` |
378
379
  | The runs that recorded the report (by CI run URL, else branch and commit); `?video=true` keeps those with a playable video; `artifactTag`, `type`, `label`, `environment`, `branch`, `outcome`, `since`, `until` narrow it ([filtering](rich-evidence.md#finding-runs-and-artifacts)) | `GET /api/reports/runs` |
379
380
  | Recorded results that carry evidence, across the whole history, found by tag, media type, label, feature, environment, branch, revision, outcome and time | `GET /api/reports/artifacts?artifactTag=demo&type=video` |
@@ -388,6 +389,21 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
388
389
  `stats` describes its recorded history: pass rate, flips, whether it is flaky and why (it passed and failed on the
389
390
  same commit, passed only after a retry, or its producer said so), durations (avg, p50, p95, min, max, last), runs
390
391
  and branches.
392
+ - The **reliability** document qualifies `stats.passRate` instead of repeating it. The snapshots one run recorded for one
393
+ producer, environment and parameter set (running, finished, enriched) are **one independent execution**, counted by its last
394
+ word with the tries it recorded; `firstAttempt` counts executions whose first try passed, `eventual` those that passed in the
395
+ end, and an execution that recorded no tries (`attemptStatuses` on its evidence) is never assumed to have passed first time.
396
+ Choose the scope with `environment`, `producer`, `parameter=key=value`, `branch`, `sha`, `specRevision`, `since` and `until`:
397
+ other environments never add to a phone's sample. By default only the newest recorded spec revision (the `fingerprint` of its
398
+ spec text, `specRevision` on each result) counts and earlier ones appear as `historical` context; `revision=any` pools them
399
+ and says so. Only `passed` and `failed` executions are trials; `not-run`, unfinished and unknown ones are named under
400
+ `excluded` and never become a pass. `sample` is `none`, `insufficient` (under `minimumExecutions`) or `sufficient`, a count
401
+ only: `notes` say when the figure pools environments, producers or branches, when failed cases were published again
402
+ (`selected-reruns`), and when history is bounded to the newest 100 results. `executions` is the denominator, one row per
403
+ independent execution with its tries and snapshots, so the same query can be inspected and shared. The Wilson interval states
404
+ its assumptions. It describes the checks Atlas holds; it is not a production failure probability. The viewer shows the same
405
+ figures under a test (scope in the address as `relEnv`, `relProducer`, `relBranch`, `relParam`, `relSince`, `relUntil`,
406
+ `relRevision=any`, `relOpen=true`).
391
407
  - The **health** document explains the numbers instead of adding one. It keeps spec files, defined specs, specs with an
392
408
  executed test, tests, outlines, examples and per-environment observations apart, and walks from the Specs total to
393
409
  the Tests total. Two spec paths are one logical spec only when a mapping says so
@@ -453,6 +469,8 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
453
469
  | `GET /api/projects/{project}/reports/tests/health` | read | Why specs, domains and counts disagree. |
454
470
  | `GET /api/reports/tests/history` | read | One test and every result recorded for it. |
455
471
  | `GET /api/projects/{project}/reports/tests/history` | read | One test and every result recorded for it. |
472
+ | `GET /api/reports/tests/reliability` | read | How reliable one test is in a scope, and how that was counted. |
473
+ | `GET /api/projects/{project}/reports/tests/reliability` | read | How reliable one test is in a scope, and how that was counted. |
456
474
  | `GET /api/reports/artifact-tags` | read | Reviewer tags of attachments: every recorded edit. |
457
475
  | `GET /api/projects/{project}/reports/artifact-tags` | read | Reviewer tags of attachments: every recorded edit. |
458
476
  | `POST /api/reports/artifact-tags` | publish | Tag or untag one attachment after upload. |
package/docs/ui.md CHANGED
@@ -87,6 +87,25 @@ 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
+ ## Reliability
91
+
92
+ An open test shows its reliability (`explorer/Reliability.tsx`, worked out by `reports/reliability` from the history the page
93
+ already holds). Never show a bare percentage: it always comes with the number of independent executions it rests on, the
94
+ "Insufficient evidence" badge below the minimum (one pass is not established reliability), the scope (environment,
95
+ producer, branch, parameters, time window, spec revision) and the warning notes (pooled environments, failed cases
96
+ published again, bounded history). First-try success is over executions that recorded their tries only; a result that
97
+ did not run or did not finish is excluded by name and never counted as a pass. The scope and the open executions table
98
+ are in the address (`rel*`), so a link reproduces the figure and the executions behind it.
99
+
100
+ ## Product latency
101
+
102
+ A test's duration and its product latency are different things and the viewer never mixes them (`explorer/ProductLatency.tsx`,
103
+ `@crvouga/atlas/reports/latency`). Statistics and the trend are labelled test execution time. Product latency is read only from
104
+ measurements named `product-latency`; it shows boundaries, group (device, build, day), samples, budget and breaches, and a
105
+ baseline comparison that lists changes of instrumentation or conditions. Percentiles and product claims that the evidence
106
+ cannot support are shown as `Withheld` with the reason, a partial interval is badged, timeouts count as samples and breaches,
107
+ and a test with no latency says it is unknown, not fast.
108
+
90
109
  ## Outcome summary
91
110
 
92
111
  A test or behavior detail states what the **selected** result says apart from what the other current sources say
@@ -170,7 +189,7 @@ The narrowly scoped exceptions are:
170
189
  | --- | --- | --- |
171
190
  | Shared shell | Project switching, spec navigation, run selection, connection status, diagnostics, mobile index | AppShell, Burger, NavLink, Menu, Popover, NativeSelect, Drawer |
172
191
  | 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 |
173
- | 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 |
192
+ | 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 |
174
193
  | 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 |
175
194
  | Statecharts | Graph, screen/event/journey details, developer metadata, chart test results | Mantine cards/navigation and product compositions; graph exception above |
176
195
  | 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.
@@ -1720,6 +1763,7 @@ with `format=markdown` (or `Accept: text/markdown`), and takes the project prefi
1720
1763
  | Every test, a summary (by status, environment, producer, domain), insights (failing, flaky, slowest) and facets | `GET /api/reports/tests` |
1721
1764
  | Only some: `q` (all words), `status` (or `flaky`), `environment`, `producer`, `domain`, `spec`, `tag`, `run`; `sort`, `offset`, `limit` | `GET /api/reports/tests?status=failed&environment=ios` |
1722
1765
  | One test: every recorded result (status, duration, environment, branch, commit, CI run), newest first | `GET /api/reports/tests/history?key=<key>` |
1766
+ | How reliable one test is in a scope (environment, producer, parameters, branch, commit, spec revision, time window): independent executions, first-try and eventual success, what was left out and why | `GET /api/reports/tests/reliability?key=<key>&environment=phone` |
1723
1767
  | Why Specs, domains and counts disagree: ambiguous spec paths, shared domains, gaps, a reconciliation of the totals | `GET /api/reports/tests/health` |
1724
1768
  | The runs that recorded the report (by CI run URL, else branch and commit); `?video=true` keeps those with a playable video; `artifactTag`, `type`, `label`, `environment`, `branch`, `outcome`, `since`, `until` narrow it ([filtering](rich-evidence.md#finding-runs-and-artifacts)) | `GET /api/reports/runs` |
1725
1769
  | Recorded results that carry evidence, across the whole history, found by tag, media type, label, feature, environment, branch, revision, outcome and time | `GET /api/reports/artifacts?artifactTag=demo&type=video` |
@@ -1734,6 +1778,21 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
1734
1778
  `stats` describes its recorded history: pass rate, flips, whether it is flaky and why (it passed and failed on the
1735
1779
  same commit, passed only after a retry, or its producer said so), durations (avg, p50, p95, min, max, last), runs
1736
1780
  and branches.
1781
+ - The **reliability** document qualifies `stats.passRate` instead of repeating it. The snapshots one run recorded for one
1782
+ producer, environment and parameter set (running, finished, enriched) are **one independent execution**, counted by its last
1783
+ word with the tries it recorded; `firstAttempt` counts executions whose first try passed, `eventual` those that passed in the
1784
+ end, and an execution that recorded no tries (`attemptStatuses` on its evidence) is never assumed to have passed first time.
1785
+ Choose the scope with `environment`, `producer`, `parameter=key=value`, `branch`, `sha`, `specRevision`, `since` and `until`:
1786
+ other environments never add to a phone's sample. By default only the newest recorded spec revision (the `fingerprint` of its
1787
+ spec text, `specRevision` on each result) counts and earlier ones appear as `historical` context; `revision=any` pools them
1788
+ and says so. Only `passed` and `failed` executions are trials; `not-run`, unfinished and unknown ones are named under
1789
+ `excluded` and never become a pass. `sample` is `none`, `insufficient` (under `minimumExecutions`) or `sufficient`, a count
1790
+ only: `notes` say when the figure pools environments, producers or branches, when failed cases were published again
1791
+ (`selected-reruns`), and when history is bounded to the newest 100 results. `executions` is the denominator, one row per
1792
+ independent execution with its tries and snapshots, so the same query can be inspected and shared. The Wilson interval states
1793
+ its assumptions. It describes the checks Atlas holds; it is not a production failure probability. The viewer shows the same
1794
+ figures under a test (scope in the address as `relEnv`, `relProducer`, `relBranch`, `relParam`, `relSince`, `relUntil`,
1795
+ `relRevision=any`, `relOpen=true`).
1737
1796
  - The **health** document explains the numbers instead of adding one. It keeps spec files, defined specs, specs with an
1738
1797
  executed test, tests, outlines, examples and per-environment observations apart, and walks from the Specs total to
1739
1798
  the Tests total. Two spec paths are one logical spec only when a mapping says so
@@ -1799,6 +1858,8 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
1799
1858
  | `GET /api/projects/{project}/reports/tests/health` | read | Why specs, domains and counts disagree. |
1800
1859
  | `GET /api/reports/tests/history` | read | One test and every result recorded for it. |
1801
1860
  | `GET /api/projects/{project}/reports/tests/history` | read | One test and every result recorded for it. |
1861
+ | `GET /api/reports/tests/reliability` | read | How reliable one test is in a scope, and how that was counted. |
1862
+ | `GET /api/projects/{project}/reports/tests/reliability` | read | How reliable one test is in a scope, and how that was counted. |
1802
1863
  | `GET /api/reports/artifact-tags` | read | Reviewer tags of attachments: every recorded edit. |
1803
1864
  | `GET /api/projects/{project}/reports/artifact-tags` | read | Reviewer tags of attachments: every recorded edit. |
1804
1865
  | `POST /api/reports/artifact-tags` | publish | Tag or untag one attachment after upload. |
@@ -2830,7 +2891,7 @@ carry any of these. Every one is optional.
2830
2891
  | `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
2892
  | `links[]` | Where to read more: the CI run, the issue, the trace client. |
2832
2893
  | `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). |
2894
+ | `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
2895
 
2835
2896
  An attachment may carry its own `tags[]` (`demo`): shown as `#demo` and searchable; see
2836
2897
  [Tagging artifacts and demos](rich-evidence.md#tagging-artifacts-and-demos).
@@ -3936,6 +3997,25 @@ when Watch is on screen.
3936
3997
  - The spec index searches titles, paths and declared domains, and can group by the domain from spec metadata (specs with
3937
3998
  none are listed under "No domain declared"; the Tests view reports domain problems).
3938
3999
 
4000
+ ## Reliability
4001
+
4002
+ An open test shows its reliability (`explorer/Reliability.tsx`, worked out by `reports/reliability` from the history the page
4003
+ already holds). Never show a bare percentage: it always comes with the number of independent executions it rests on, the
4004
+ "Insufficient evidence" badge below the minimum (one pass is not established reliability), the scope (environment,
4005
+ producer, branch, parameters, time window, spec revision) and the warning notes (pooled environments, failed cases
4006
+ published again, bounded history). First-try success is over executions that recorded their tries only; a result that
4007
+ did not run or did not finish is excluded by name and never counted as a pass. The scope and the open executions table
4008
+ are in the address (`rel*`), so a link reproduces the figure and the executions behind it.
4009
+
4010
+ ## Product latency
4011
+
4012
+ A test's duration and its product latency are different things and the viewer never mixes them (`explorer/ProductLatency.tsx`,
4013
+ `@crvouga/atlas/reports/latency`). Statistics and the trend are labelled test execution time. Product latency is read only from
4014
+ measurements named `product-latency`; it shows boundaries, group (device, build, day), samples, budget and breaches, and a
4015
+ baseline comparison that lists changes of instrumentation or conditions. Percentiles and product claims that the evidence
4016
+ cannot support are shown as `Withheld` with the reason, a partial interval is badged, timeouts count as samples and breaches,
4017
+ and a test with no latency says it is unknown, not fast.
4018
+
3939
4019
  ## Outcome summary
3940
4020
 
3941
4021
  A test or behavior detail states what the **selected** result says apart from what the other current sources say
@@ -4019,7 +4099,7 @@ The narrowly scoped exceptions are:
4019
4099
  | --- | --- | --- |
4020
4100
  | Shared shell | Project switching, spec navigation, run selection, connection status, diagnostics, mobile index | AppShell, Burger, NavLink, Menu, Popover, NativeSelect, Drawer |
4021
4101
  | 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 |
4022
- | 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 |
4102
+ | 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 |
4023
4103
  | 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 |
4024
4104
  | Statecharts | Graph, screen/event/journey details, developer metadata, chart test results | Mantine cards/navigation and product compositions; graph exception above |
4025
4105
  | Evidence | Steps, retries, annotations, facts, unknown extensions, safe downloads and previews | List, Badge, Accordion, Code, Image, native media |
@@ -5156,6 +5236,54 @@ Scoped to one project. The same project may be selected on the unprefixed API us
5156
5236
 
5157
5237
  Responses: `200` The test and its history.; `304` Unchanged since the `If-None-Match` ETag.; `400` No `key`.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `404` No test in the report has that key.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
5158
5238
 
5239
+ ### How reliable one test is in a scope, and how that was counted
5240
+
5241
+ `GET /api/reports/tests/reliability` · role: read
5242
+
5243
+ A success rate for one test that says what it counted. The snapshots one run recorded for one producer, environment and parameter set (running, finished, enriched) are one independent execution; its last word counts once with the tries it recorded. `eventual` is the share of executions that passed in the end, `firstAttempt` the share whose first try passed (over executions that recorded their tries; the rest are `unrecordedAttempts`, never assumed to have passed first time).
5244
+
5245
+ Choose the scope with `environment`, `producer`, `parameter` (`key=value`), `branch`, `sha`, `specRevision`, `since` and `until`; executions elsewhere never count. By default only the newest recorded spec revision counts and earlier ones appear under `historical`; send `revision=any` to pool them. Only `passed` and `failed` executions are trials: `not-run` and unfinished ones are listed under `excluded` and never counted as a pass. `sample` is `none`, `insufficient` (fewer than `minimumExecutions`) or `sufficient`, a count only: `notes` say when environments, producers or branches are pooled, failed cases were published again, or history is bounded to the newest 100 results. `executions` is the denominator, open to inspection. The interval states its assumptions. This is not a production failure probability. Send `format=markdown` for Markdown.
5246
+
5247
+ - `key` (query, required): The test's `key` from `/api/reports/tests`.
5248
+ - `environment` (query): Environments or devices to count. Comma-separated.
5249
+ - `producer` (query): Producers to count. Comma-separated.
5250
+ - `parameter` (query): Parameter choices `key=value`; all must match. Comma-separated.
5251
+ - `branch` (query): Branches to count. Comma-separated.
5252
+ - `sha` (query): Commits: a full id or a prefix of at least seven characters. Comma-separated.
5253
+ - `specRevision` (query): Only executions recorded against these spec fingerprints (overrides `revision`). Comma-separated.
5254
+ - `revision` (query): `any` pools every spec revision; otherwise only the newest recorded one counts.
5255
+ - `since` (query): Only executions at or after this time: epoch milliseconds or a date.
5256
+ - `until` (query): Only executions at or before this time: epoch milliseconds or a date.
5257
+ - `format` (query): `markdown` for Markdown instead of JSON.
5258
+ - `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
5259
+
5260
+ Responses: `200` The reliability of the test in the scope.; `304` Unchanged since the `If-None-Match` ETag.; `400` No `key`.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `404` No test in the report has that key.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
5261
+
5262
+ ### How reliable one test is in a scope, and how that was counted
5263
+
5264
+ `GET /api/projects/{project}/reports/tests/reliability` · role: read
5265
+
5266
+ A success rate for one test that says what it counted. The snapshots one run recorded for one producer, environment and parameter set (running, finished, enriched) are one independent execution; its last word counts once with the tries it recorded. `eventual` is the share of executions that passed in the end, `firstAttempt` the share whose first try passed (over executions that recorded their tries; the rest are `unrecordedAttempts`, never assumed to have passed first time).
5267
+
5268
+ Choose the scope with `environment`, `producer`, `parameter` (`key=value`), `branch`, `sha`, `specRevision`, `since` and `until`; executions elsewhere never count. By default only the newest recorded spec revision counts and earlier ones appear under `historical`; send `revision=any` to pool them. Only `passed` and `failed` executions are trials: `not-run` and unfinished ones are listed under `excluded` and never counted as a pass. `sample` is `none`, `insufficient` (fewer than `minimumExecutions`) or `sufficient`, a count only: `notes` say when environments, producers or branches are pooled, failed cases were published again, or history is bounded to the newest 100 results. `executions` is the denominator, open to inspection. The interval states its assumptions. This is not a production failure probability. Send `format=markdown` for Markdown.
5269
+
5270
+ Scoped to one project. The same project may be selected on the unprefixed API using ?project= or X-Atlas-Project. Unknown projects return 404 on reads and are created by an authenticated upload.
5271
+
5272
+ - `project` (path, required): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
5273
+ - `key` (query, required): The test's `key` from `/api/reports/tests`.
5274
+ - `environment` (query): Environments or devices to count. Comma-separated.
5275
+ - `producer` (query): Producers to count. Comma-separated.
5276
+ - `parameter` (query): Parameter choices `key=value`; all must match. Comma-separated.
5277
+ - `branch` (query): Branches to count. Comma-separated.
5278
+ - `sha` (query): Commits: a full id or a prefix of at least seven characters. Comma-separated.
5279
+ - `specRevision` (query): Only executions recorded against these spec fingerprints (overrides `revision`). Comma-separated.
5280
+ - `revision` (query): `any` pools every spec revision; otherwise only the newest recorded one counts.
5281
+ - `since` (query): Only executions at or after this time: epoch milliseconds or a date.
5282
+ - `until` (query): Only executions at or before this time: epoch milliseconds or a date.
5283
+ - `format` (query): `markdown` for Markdown instead of JSON.
5284
+
5285
+ Responses: `200` The reliability of the test in the scope.; `304` Unchanged since the `If-None-Match` ETag.; `400` No `key`.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `404` No test in the report has that key.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
5286
+
5159
5287
  ### Reviewer tags of attachments: every recorded edit
5160
5288
 
5161
5289
  `GET /api/reports/artifact-tags` · role: read
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crvouga/atlas",
3
- "version": "1.7.0",
3
+ "version": "1.9.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",
@@ -56,8 +56,63 @@
56
56
  "type": "string"
57
57
  },
58
58
  "limit": {
59
- "description": "The allowance. A measurement over it is reported, and never fails the check by itself",
59
+ "description": "The allowance, or budget. A measurement over it is reported, and never fails the check by itself",
60
60
  "type": "number"
61
+ },
62
+ "kind": {
63
+ "description": "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",
64
+ "type": "string",
65
+ "minLength": 1
66
+ },
67
+ "from": {
68
+ "description": "The event the interval starts at. Without both `from` and `to` a latency is shown but no product claim is made from it",
69
+ "type": "string",
70
+ "minLength": 1
71
+ },
72
+ "to": {
73
+ "description": "The event the interval ends at",
74
+ "type": "string",
75
+ "minLength": 1
76
+ },
77
+ "intended": {
78
+ "description": "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",
79
+ "type": "object",
80
+ "properties": {
81
+ "from": {
82
+ "description": "The event the interval starts at: `end of speech`, `submit pressed`",
83
+ "type": "string",
84
+ "minLength": 1
85
+ },
86
+ "to": {
87
+ "description": "The event the interval ends at: `audible response`, `confirmation visible`",
88
+ "type": "string",
89
+ "minLength": 1
90
+ }
91
+ },
92
+ "additionalProperties": {}
93
+ },
94
+ "source": {
95
+ "description": "How it was measured: `app-instrumentation`, `trace`, `recording-analysis`",
96
+ "type": "string"
97
+ },
98
+ "outcome": {
99
+ "description": "`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",
100
+ "type": "string",
101
+ "minLength": 1
102
+ },
103
+ "instrumentation": {
104
+ "description": "The version or method of the measurement. A change of it between two sets of observations is disclosed when they are compared",
105
+ "type": "string"
106
+ },
107
+ "labels": {
108
+ "description": "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",
109
+ "type": "object",
110
+ "propertyNames": {
111
+ "type": "string"
112
+ },
113
+ "additionalProperties": {
114
+ "type": "string"
115
+ }
61
116
  }
62
117
  },
63
118
  "required": [
@@ -57,16 +57,74 @@
57
57
  "type": "object",
58
58
  "properties": {
59
59
  "name": {
60
- "type": "string"
60
+ "type": "string",
61
+ "minLength": 1
61
62
  },
62
63
  "value": {
63
- "type": "number"
64
+ "type": "number",
65
+ "description": "A finite number"
64
66
  },
65
67
  "unit": {
66
68
  "type": "string"
67
69
  },
68
70
  "limit": {
71
+ "description": "The allowance, or budget. A measurement over it is reported, and never fails the check by itself",
69
72
  "type": "number"
73
+ },
74
+ "kind": {
75
+ "description": "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",
76
+ "type": "string",
77
+ "minLength": 1
78
+ },
79
+ "from": {
80
+ "description": "The event the interval starts at. Without both `from` and `to` a latency is shown but no product claim is made from it",
81
+ "type": "string",
82
+ "minLength": 1
83
+ },
84
+ "to": {
85
+ "description": "The event the interval ends at",
86
+ "type": "string",
87
+ "minLength": 1
88
+ },
89
+ "intended": {
90
+ "description": "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",
91
+ "type": "object",
92
+ "properties": {
93
+ "from": {
94
+ "description": "The event the interval starts at: `end of speech`, `submit pressed`",
95
+ "type": "string",
96
+ "minLength": 1
97
+ },
98
+ "to": {
99
+ "description": "The event the interval ends at: `audible response`, `confirmation visible`",
100
+ "type": "string",
101
+ "minLength": 1
102
+ }
103
+ },
104
+ "additionalProperties": {}
105
+ },
106
+ "source": {
107
+ "description": "How it was measured: `app-instrumentation`, `trace`, `recording-analysis`",
108
+ "type": "string"
109
+ },
110
+ "outcome": {
111
+ "description": "`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",
112
+ "type": "string",
113
+ "minLength": 1
114
+ },
115
+ "instrumentation": {
116
+ "description": "The version or method of the measurement. A change of it between two sets of observations is disclosed when they are compared",
117
+ "type": "string"
118
+ },
119
+ "labels": {
120
+ "description": "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",
121
+ "type": "object",
122
+ "propertyNames": {
123
+ "type": "string"
124
+ },
125
+ "additionalProperties": {
126
+ "type": "string"
127
+ }
70
128
  }
71
129
  },
72
130
  "required": [
@@ -121,6 +179,19 @@
121
179
  "minimum": 0,
122
180
  "maximum": 9007199254740991
123
181
  },
182
+ "attemptStatuses": {
183
+ "description": "The status of each recorded try, in the order they were made. Absent when no try was recorded, and in reports written before this was recorded: a first try is then unknown, never assumed to have passed",
184
+ "type": "array",
185
+ "items": {
186
+ "type": "string",
187
+ "enum": [
188
+ "passed",
189
+ "failed",
190
+ "running",
191
+ "not-run"
192
+ ]
193
+ }
194
+ },
124
195
  "links": {
125
196
  "type": "integer",
126
197
  "minimum": 0,
@@ -514,6 +585,19 @@
514
585
  "minimum": 0,
515
586
  "maximum": 9007199254740991
516
587
  },
588
+ "attemptStatuses": {
589
+ "description": "The status of each recorded try, in the order they were made. Absent when no try was recorded, and in reports written before this was recorded: a first try is then unknown, never assumed to have passed",
590
+ "type": "array",
591
+ "items": {
592
+ "type": "string",
593
+ "enum": [
594
+ "passed",
595
+ "failed",
596
+ "running",
597
+ "not-run"
598
+ ]
599
+ }
600
+ },
517
601
  "links": {
518
602
  "type": "integer",
519
603
  "minimum": 0,