@crvouga/atlas 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/reports/src/evidence-detail.d.ts +14 -3
- package/dist/reports/src/evidence-detail.js +33 -19
- package/dist/reports/src/evidence-scope.d.ts +83 -0
- package/dist/reports/src/evidence-scope.js +129 -0
- package/dist/reports/src/evidence-types.d.ts +3 -1
- package/dist/reports/src/explorer-types.d.ts +1 -1
- package/dist/reports/src/layer-merge.js +3 -2
- package/dist/reports/src/layer-types.d.ts +1 -1
- package/dist/reports/src/publish-checks.js +1 -0
- package/dist/reports/src/reporter.d.ts +1 -1
- package/dist/reports/src/reports-explorer-route.d.ts +276 -0
- package/dist/reports/src/reports-route.d.ts +276 -0
- package/dist/reports/src/test-catalog.d.ts +7 -1
- package/dist/reports/src/test-catalog.js +15 -3
- package/dist/schema/src/api.d.ts +520 -0
- package/dist/schema/src/api.js +7 -4
- package/dist/schema/src/layer.d.ts +981 -0
- package/dist/schema/src/layer.js +43 -4
- package/dist/server/src/openapi.js +1 -0
- package/docs/extending.md +1 -0
- package/docs/reference/http-api.md +2 -0
- package/docs/reference/reports.md +179 -7
- package/docs/reference/schema.md +23 -0
- package/docs/rich-evidence.md +47 -0
- package/llms-full.txt +50 -0
- package/package.json +1 -1
- package/packages/schema/schemas/check-detail.schema.json +673 -1
- package/packages/schema/schemas/effective-delta.schema.json +192 -0
- package/packages/schema/schemas/effective-evidence.schema.json +192 -0
- package/packages/schema/schemas/evidence-page.schema.json +96 -0
- package/packages/schema/schemas/layer-rows.schema.json +96 -0
- package/packages/schema/schemas/layer.schema.json +1598 -62
- package/packages/schema/schemas/publish-checks.schema.json +768 -0
- package/packages/schema/schemas/report-run.schema.json +96 -0
- package/packages/schema/schemas/test-catalog.schema.json +128 -0
- package/packages/schema/schemas/test-history.schema.json +202 -0
- package/packages/server/openapi.json +2581 -23
- package/packages/visualizer/src/evidence/artifacts.tsx +24 -18
- package/packages/visualizer/src/evidence/evidence-scope.tsx +106 -0
- package/packages/visualizer/src/evidence/format.ts +11 -0
- package/packages/visualizer/src/explorer/Explorer.tsx +19 -0
- package/packages/visualizer/src/explorer/search.ts +5 -1
package/dist/schema/src/layer.js
CHANGED
|
@@ -88,6 +88,31 @@ export const MomentSchema = z.looseObject({
|
|
|
88
88
|
step: z.string().optional().describe('The `id` of a step'),
|
|
89
89
|
offsetMs: z.number().nonnegative().optional()
|
|
90
90
|
});
|
|
91
|
+
/**
|
|
92
|
+
* What a piece of evidence does and does not show, declared by the producer that made it: where it ran, whether its
|
|
93
|
+
* data and dependencies were real, whether audio was captured, how it was paced and which assertions the result
|
|
94
|
+
* stands on. Every field is optional and a field that is absent is unknown, never assumed live, physical or end to end.
|
|
95
|
+
*
|
|
96
|
+
* It is a declaration, not a finding: Atlas shows it as the producer's word, apart from the dimensions the producer
|
|
97
|
+
* names in `verified` (facts something independent checked). It describes evidence and never decides a status, a slot
|
|
98
|
+
* or a winner. The nearest declaration wins as a whole, never field by field: an attachment's `scope`, else its
|
|
99
|
+
* attempt's, else its check's, else its part's, else its layer's.
|
|
100
|
+
*/
|
|
101
|
+
export const EvidenceScopeSchema = z.looseObject({
|
|
102
|
+
surface: z.string().min(1).optional().describe('Where the product ran, in an open vocabulary: `browser`, `native-app`, `api`, `cli`…'),
|
|
103
|
+
deviceKind: z.string().min(1).optional().describe('What it ran on, in an open vocabulary: `simulator`, `emulator`, `physical`, `virtual-machine`, `desktop`… A simulator and a physical phone stay distinct values; they are filtered separately'),
|
|
104
|
+
syntheticData: z.boolean().optional().describe('True: the data was made up for the run. False: real data. Absent: unknown'),
|
|
105
|
+
dependencies: z.string().min(1).optional().describe('Whether what the product talks to was `scripted` (replies fixed in advance), `live` or `mixed`. An open vocabulary; absent is unknown'),
|
|
106
|
+
audioCaptured: z.boolean().optional().describe('True: the recording has real audio. False: it is silent. Absent: unknown'),
|
|
107
|
+
pacing: z.string().min(1).optional().describe('How time ran, in an open vocabulary: `paced` (slowed or scripted for a viewer), `real-time`. Absent: unknown'),
|
|
108
|
+
asserted: z.array(z.string()).optional().describe('The boundaries the result actually asserts. Activity visible in a recording beyond them is not verified behavior'),
|
|
109
|
+
limitations: z.array(z.string()).optional().describe('Known limits of what this evidence shows, in the producer\'s words'),
|
|
110
|
+
declaredBy: z.string().optional().describe('Who or what made this declaration: a tool, a team, a person'),
|
|
111
|
+
verified: z.array(z.string()).optional().describe('The names of the fields above (`surface`, `deviceKind`, `syntheticData`, `dependencies`, `audioCaptured`, `pacing`) that something independent of the producer checked. Every other field is only declared'),
|
|
112
|
+
verifiedBy: z.string().optional().describe('What checked the `verified` fields'),
|
|
113
|
+
provenance: z.array(LinkSchema).optional().describe('Where the declaration comes from and how to check it: a document, a CI step, a device log'),
|
|
114
|
+
extensions: ExtensionsSchema.optional()
|
|
115
|
+
});
|
|
91
116
|
/**
|
|
92
117
|
* A file or a value that goes with a result: a screenshot, a video, a trace, a log, a DOM or accessibility
|
|
93
118
|
* snapshot, a HAR, a coverage report, a diff, anything. `contentType` says how to read it; `role` says what
|
|
@@ -110,6 +135,7 @@ export const AttachmentSchema = z.looseObject({
|
|
|
110
135
|
durationMs: z.number().nonnegative().optional().describe('For video and audio'),
|
|
111
136
|
description: z.string().optional(),
|
|
112
137
|
omitted: z.string().optional().describe('Why the producer did not include its bytes, in an open vocabulary: too-large, not-found, not-allowed, over-budget, policy… Present on a metadata-only attachment, so a reader can tell "left out" from "never recorded"'),
|
|
138
|
+
scope: EvidenceScopeSchema.optional().describe('What this one attachment (usually a recording) does and does not show. Overrides the scope of its attempt, check, part and layer as a whole'),
|
|
113
139
|
tags: z.array(z.string()).optional().describe('Free labels of this one attachment, such as `demo`: shown as `#demo`, searchable, and normalized by `normalizeTag` (a leading `#` and letter case never matter). Unlike the tags of a check they say nothing about its result'),
|
|
114
140
|
at: MomentSchema.optional(),
|
|
115
141
|
poster: z.string().optional().describe('The `id` of an attachment that stands for this one before it is opened'),
|
|
@@ -190,6 +216,7 @@ export const AttemptSchema = z.looseObject({
|
|
|
190
216
|
durationMs: z.number().nonnegative().optional(),
|
|
191
217
|
startedAtMs: z.number().nonnegative().optional(),
|
|
192
218
|
detail: z.string().optional(),
|
|
219
|
+
scope: EvidenceScopeSchema.optional().describe('What this try\'s evidence shows, when it differs from the other tries (a retry on another device). Overrides the check\'s scope for this try\'s attachments'),
|
|
193
220
|
steps: z.array(StepSchema).optional(),
|
|
194
221
|
attachments: z.array(AttachmentSchema).optional(),
|
|
195
222
|
annotations: z.array(AnnotationSchema).optional(),
|
|
@@ -202,6 +229,7 @@ const evidenceShape = {
|
|
|
202
229
|
links: z.array(LinkSchema).optional(),
|
|
203
230
|
attachments: z.array(AttachmentSchema).optional(),
|
|
204
231
|
annotations: z.array(AnnotationSchema).optional(),
|
|
232
|
+
scope: EvidenceScopeSchema.optional().describe('What the evidence below this level does and does not show: where it ran, how real its data and dependencies were, whether audio was captured, and what is asserted. Absent: unknown, never assumed live, physical or end to end'),
|
|
205
233
|
extensions: ExtensionsSchema.optional()
|
|
206
234
|
};
|
|
207
235
|
/**
|
|
@@ -329,17 +357,27 @@ function lenientList(value, schema, prepare = (item) => item) {
|
|
|
329
357
|
});
|
|
330
358
|
}
|
|
331
359
|
const object = (value) => (typeof value === 'object' && value !== null && !Array.isArray(value) ? value : null);
|
|
332
|
-
/**
|
|
360
|
+
/** A scope that cannot be read is no scope: the evidence is kept and its scope is unknown. */
|
|
361
|
+
function lenientScope(holder) {
|
|
362
|
+
if (!('scope' in holder))
|
|
363
|
+
return holder;
|
|
364
|
+
const { scope, ...rest } = holder;
|
|
365
|
+
const parsed = EvidenceScopeSchema.safeParse(scope);
|
|
366
|
+
return parsed.success ? { ...rest, scope: parsed.data } : rest;
|
|
367
|
+
}
|
|
368
|
+
/** An attachment whose `tags` or `scope` cannot be read keeps its file: only the tags that are strings are kept, and an unreadable scope is dropped. */
|
|
333
369
|
function lenientAttachment(item) {
|
|
334
370
|
const attachment = object(item);
|
|
335
|
-
if (!attachment
|
|
371
|
+
if (!attachment)
|
|
336
372
|
return item;
|
|
337
|
-
const { tags, ...rest } = attachment;
|
|
373
|
+
const { tags, ...rest } = lenientScope(attachment);
|
|
374
|
+
if (!('tags' in attachment))
|
|
375
|
+
return rest;
|
|
338
376
|
const kept = lenientList(tags, z.string());
|
|
339
377
|
return kept ? { ...rest, tags: kept } : rest;
|
|
340
378
|
}
|
|
341
379
|
function lenientEvidence(holder, nested = []) {
|
|
342
|
-
const out =
|
|
380
|
+
const out = lenientScope(holder);
|
|
343
381
|
const set = (key, value) => { if (value === undefined)
|
|
344
382
|
delete out[key];
|
|
345
383
|
else
|
|
@@ -468,6 +506,7 @@ export const PublishChecksSchema = z.looseObject({
|
|
|
468
506
|
links: evidenceShape.links,
|
|
469
507
|
attachments: z.array(AttachmentInputSchema).optional().describe('Attached to the whole layer: a CI log, a coverage report, a trace archive'),
|
|
470
508
|
annotations: evidenceShape.annotations,
|
|
509
|
+
scope: evidenceShape.scope,
|
|
471
510
|
extensions: evidenceShape.extensions
|
|
472
511
|
});
|
|
473
512
|
const CountsSchema = z.object({
|
|
@@ -293,6 +293,7 @@ const BASE_OPERATIONS = [
|
|
|
293
293
|
{ name: 'tag', in: 'query', description: 'Only tests with this tag. Repeatable or comma-separated.', schema: str },
|
|
294
294
|
{ name: 'artifactTag', in: 'query', description: 'Only tests with an attachment tagged this (`demo` and `#demo` are the same tag), not counting tags inherited from a check or run: the exact artifact query. `inherited=1` widens it to the tags inherited from the check and run. Repeatable or comma-separated.', schema: str, example: 'demo' },
|
|
295
295
|
{ name: 'inherited', in: 'query', description: 'With `artifactTag`: `1` also matches the tags an attachment inherits from its check and run, so a recording without a tag of its own under a run tagged `demo` is found. Without it the query is exact.', schema: { type: 'string', enum: ['1', 'true'] } },
|
|
296
|
+
{ name: 'scope', in: 'query', description: 'Only tests with one playable recording that states every one of these producer-declared qualifications: `surface:<value>`, `device:<value>` (`device:physical` and `device:simulator` stay apart), `data:synthetic` or `data:real`, `dependencies:<value>`, `audio:recorded` or `audio:silent`, `pacing:<value>`. All must hold for the same recording. A recording that did not declare a value never matches it, and `unknown` matches only a recording that declared no scope. Repeatable or comma-separated.', schema: str, example: 'device:physical,audio:recorded' },
|
|
296
297
|
{ name: 'run', in: 'query', description: 'Only tests recorded in this run (a `key` from `/api/reports/runs`).', schema: str },
|
|
297
298
|
{ name: 'sort', in: 'query', description: 'The order: `spec` (default: domain, spec, title), `title`, `recent`, `slowest`, `failures`, `flaky` or `runs`.', schema: { type: 'string', enum: ['spec', 'title', 'recent', 'slowest', 'failures', 'flaky', 'runs'] } },
|
|
298
299
|
{ name: 'offset', in: 'query', description: 'Skip this many matching tests.', schema: { type: 'integer', minimum: 0 } },
|
package/docs/extending.md
CHANGED
|
@@ -31,6 +31,7 @@ carry any of these. Every one is optional.
|
|
|
31
31
|
| `attempts[]` | Every try, when the check was retried. |
|
|
32
32
|
| `tags[]` | Free labels to filter by: `smoke`, `payments`. |
|
|
33
33
|
| `labels{}` | Facts about the result that are not its identity: `owner`, `severity`, `browser`, `build`. |
|
|
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). |
|
|
34
35
|
| `links[]` | Where to read more: the CI run, the issue, the trace client. |
|
|
35
36
|
| `extensions{}` | Structured data of your own or a tool's, under a name you own. |
|
|
36
37
|
| `measurements[]` | Numbers, with units and an allowance (already part of the contract). |
|
|
@@ -497,6 +497,7 @@ Send `format=markdown` (or `Accept: text/markdown`) for the same as a Markdown r
|
|
|
497
497
|
- `tag` (query): Only tests with this tag. Repeatable or comma-separated.
|
|
498
498
|
- `artifactTag` (query): Only tests with an attachment tagged this (`demo` and `#demo` are the same tag), not counting tags inherited from a check or run: the exact artifact query. `inherited=1` widens it to the tags inherited from the check and run. Repeatable or comma-separated.
|
|
499
499
|
- `inherited` (query): With `artifactTag`: `1` also matches the tags an attachment inherits from its check and run, so a recording without a tag of its own under a run tagged `demo` is found. Without it the query is exact.
|
|
500
|
+
- `scope` (query): Only tests with one playable recording that states every one of these producer-declared qualifications: `surface:<value>`, `device:<value>` (`device:physical` and `device:simulator` stay apart), `data:synthetic` or `data:real`, `dependencies:<value>`, `audio:recorded` or `audio:silent`, `pacing:<value>`. All must hold for the same recording. A recording that did not declare a value never matches it, and `unknown` matches only a recording that declared no scope. Repeatable or comma-separated.
|
|
500
501
|
- `run` (query): Only tests recorded in this run (a `key` from `/api/reports/runs`).
|
|
501
502
|
- `sort` (query): The order: `spec` (default: domain, spec, title), `title`, `recent`, `slowest`, `failures`, `flaky` or `runs`.
|
|
502
503
|
- `offset` (query): Skip this many matching tests.
|
|
@@ -528,6 +529,7 @@ Scoped to one project. The same project may be selected on the unprefixed API us
|
|
|
528
529
|
- `tag` (query): Only tests with this tag. Repeatable or comma-separated.
|
|
529
530
|
- `artifactTag` (query): Only tests with an attachment tagged this (`demo` and `#demo` are the same tag), not counting tags inherited from a check or run: the exact artifact query. `inherited=1` widens it to the tags inherited from the check and run. Repeatable or comma-separated.
|
|
530
531
|
- `inherited` (query): With `artifactTag`: `1` also matches the tags an attachment inherits from its check and run, so a recording without a tag of its own under a run tagged `demo` is found. Without it the query is exact.
|
|
532
|
+
- `scope` (query): Only tests with one playable recording that states every one of these producer-declared qualifications: `surface:<value>`, `device:<value>` (`device:physical` and `device:simulator` stay apart), `data:synthetic` or `data:real`, `dependencies:<value>`, `audio:recorded` or `audio:silent`, `pacing:<value>`. All must hold for the same recording. A recording that did not declare a value never matches it, and `unknown` matches only a recording that declared no scope. Repeatable or comma-separated.
|
|
531
533
|
- `run` (query): Only tests recorded in this run (a `key` from `/api/reports/runs`).
|
|
532
534
|
- `sort` (query): The order: `spec` (default: domain, spec, title), `title`, `recent`, `slowest`, `failures`, `flaky` or `runs`.
|
|
533
535
|
- `offset` (query): Skip this many matching tests.
|
|
@@ -1661,6 +1661,8 @@ export type EvidenceHolder = {
|
|
|
1661
1661
|
readonly steps?: readonly Step[];
|
|
1662
1662
|
readonly attempts?: readonly Attempt[];
|
|
1663
1663
|
readonly extensions?: Readonly<Record<string, unknown>>;
|
|
1664
|
+
/** What its evidence shows, as its producer declared it (see `EvidenceScope`). */
|
|
1665
|
+
readonly scope?: EvidenceScope;
|
|
1664
1666
|
};
|
|
1665
1667
|
```
|
|
1666
1668
|
|
|
@@ -1680,7 +1682,17 @@ Several holders (for example a check and its layer) are summed: `attachments` al
|
|
|
1680
1682
|
attempts, `steps` counts only each holder's own top-level steps, tags are de-duplicated, and for a repeated
|
|
1681
1683
|
label the later holder wins. `artifactTags` are the attachments' own normalized tags and are never inherited. `roles` are the attachments' roles (or, without one, what their media type or file
|
|
1682
1684
|
name implies), sorted and distinct. `videos` lists every attachment whose effective media type is video, with the
|
|
1683
|
-
check, retry attempt or step that holds it (see {@link isVideoAttachment}).
|
|
1685
|
+
check, retry attempt or step that holds it (see {@link isVideoAttachment}), each with the scope that applies to it.
|
|
1686
|
+
|
|
1687
|
+
#### `summarizeEvidenceIn`
|
|
1688
|
+
|
|
1689
|
+
```ts
|
|
1690
|
+
export function summarizeEvidenceIn(inherited: EvidenceScope | undefined, ...holders: readonly EvidenceHolder[]): EvidenceSummary | undefined
|
|
1691
|
+
```
|
|
1692
|
+
|
|
1693
|
+
Like {@link summarizeEvidence} for the checks of a part: a video that declares no scope, and sits in a holder that
|
|
1694
|
+
declares none, takes the scope `inherited` from its part or layer (the nearest declaration of the two, see
|
|
1695
|
+
`effectiveScope`). Without `inherited` it is the same as `summarizeEvidence`.
|
|
1684
1696
|
|
|
1685
1697
|
#### `summarizeLayerEvidence`
|
|
1686
1698
|
|
|
@@ -1730,6 +1742,8 @@ export type CheckDetail = {
|
|
|
1730
1742
|
readonly check: Check;
|
|
1731
1743
|
/** What the run as a whole carries: its logs, its links, its labels. */
|
|
1732
1744
|
readonly run: EvidenceHolder;
|
|
1745
|
+
/** The scope the check inherits: its part's declaration, else its layer's. Absent when neither declared one, which is unknown, not live or physical. */
|
|
1746
|
+
readonly inheritedScope?: EvidenceScope;
|
|
1733
1747
|
};
|
|
1734
1748
|
```
|
|
1735
1749
|
|
|
@@ -1738,7 +1752,7 @@ All of one result's evidence, read when someone opens it.
|
|
|
1738
1752
|
#### `checkDetailOf`
|
|
1739
1753
|
|
|
1740
1754
|
```ts
|
|
1741
|
-
export function checkDetailOf(layer: { readonly id: string; readonly parts: readonly { readonly kind: string; readonly checks: readonly Check[] }[] } & EvidenceHolder, checkId: string, kind?: string): CheckDetail | null
|
|
1755
|
+
export function checkDetailOf(layer: { readonly id: string; readonly parts: readonly { readonly kind: string; readonly checks: readonly Check[]; readonly scope?: EvidenceScope }[] } & EvidenceHolder, checkId: string, kind?: string): CheckDetail | null
|
|
1742
1756
|
```
|
|
1743
1757
|
|
|
1744
1758
|
One result's evidence, from a layer: the first check with id `checkId` (in a part of kind `kind`, when given) plus the layer's own evidence. Null when the layer holds no such check.
|
|
@@ -1749,7 +1763,7 @@ One result's evidence, from a layer: the first check with id `checkId` (in a par
|
|
|
1749
1763
|
export function runEvidence(layer: EvidenceHolder): EvidenceHolder
|
|
1750
1764
|
```
|
|
1751
1765
|
|
|
1752
|
-
What a layer carries as a whole (its tags, labels, links, attachments, annotations and extensions; not its parts' checks). Absent fields stay absent.
|
|
1766
|
+
What a layer carries as a whole (its scope, tags, labels, links, attachments, annotations and extensions; not its parts' checks). Absent fields stay absent.
|
|
1753
1767
|
|
|
1754
1768
|
#### `bundleKeysOf`
|
|
1755
1769
|
|
|
@@ -1844,6 +1858,156 @@ the next pass; `refresh` does not throw for those.
|
|
|
1844
1858
|
|
|
1845
1859
|
- `args.backend`: The backend id stamped on live layers.
|
|
1846
1860
|
|
|
1861
|
+
### Evidence scope
|
|
1862
|
+
|
|
1863
|
+
Source: `packages/reports/src/evidence-scope.ts`. Import: `@crvouga/atlas/reports/evidence-scope`
|
|
1864
|
+
|
|
1865
|
+
Reads what a producer declared a piece of evidence shows (`EvidenceScope`): which declaration applies to an
|
|
1866
|
+
attachment, the short badges a reviewer sees before playing a recording, the facts split into declared and
|
|
1867
|
+
independently verified, and the filter tokens (`device:physical`, `audio:recorded`) a query narrows by. Pure
|
|
1868
|
+
functions, safe in a browser.
|
|
1869
|
+
|
|
1870
|
+
The one rule: what is not declared is unknown. A missing scope, or a missing field, never reads as live, physical,
|
|
1871
|
+
audible or end to end, and a filter for a value never matches evidence that did not say it. A scope describes
|
|
1872
|
+
evidence; it never decides a status, a slot or a winner.
|
|
1873
|
+
|
|
1874
|
+
See docs/rich-evidence.md#evidence-scope
|
|
1875
|
+
|
|
1876
|
+
#### `SCOPE_DIMENSIONS`
|
|
1877
|
+
|
|
1878
|
+
```ts
|
|
1879
|
+
export const SCOPE_DIMENSIONS = …
|
|
1880
|
+
```
|
|
1881
|
+
|
|
1882
|
+
The fields of a scope that say something about the evidence and that `verified` may name, in the order they are shown.
|
|
1883
|
+
|
|
1884
|
+
#### `ScopeDimension`
|
|
1885
|
+
|
|
1886
|
+
```ts
|
|
1887
|
+
export type ScopeDimension = (typeof SCOPE_DIMENSIONS)[number];
|
|
1888
|
+
```
|
|
1889
|
+
|
|
1890
|
+
One of {@link SCOPE_DIMENSIONS}.
|
|
1891
|
+
|
|
1892
|
+
#### `UNKNOWN_SCOPE`
|
|
1893
|
+
|
|
1894
|
+
```ts
|
|
1895
|
+
export const UNKNOWN_SCOPE = …
|
|
1896
|
+
```
|
|
1897
|
+
|
|
1898
|
+
The filter token of a recording that declares no scope at all.
|
|
1899
|
+
|
|
1900
|
+
#### `effectiveScope`
|
|
1901
|
+
|
|
1902
|
+
```ts
|
|
1903
|
+
export function effectiveScope(...candidates: readonly (EvidenceScope | undefined)[]): EvidenceScope | undefined
|
|
1904
|
+
```
|
|
1905
|
+
|
|
1906
|
+
The declaration that applies to something: the nearest one wins as a whole, never field by field, so a recording
|
|
1907
|
+
never shows a limitation or a device that was declared for another source. Pass the candidates nearest first
|
|
1908
|
+
(attachment, attempt, check, part, layer); absent ones are skipped. Undefined when none declares a scope.
|
|
1909
|
+
|
|
1910
|
+
**Example**
|
|
1911
|
+
|
|
1912
|
+
```ts
|
|
1913
|
+
effectiveScope(attachment.scope, attempt.scope, check.scope, layer.scope);
|
|
1914
|
+
```
|
|
1915
|
+
|
|
1916
|
+
#### `scopeKey`
|
|
1917
|
+
|
|
1918
|
+
```ts
|
|
1919
|
+
export function scopeKey(scope: EvidenceScope | undefined): string
|
|
1920
|
+
```
|
|
1921
|
+
|
|
1922
|
+
A stable string that is equal for equal declarations (key order never matters), to tell one scope from another.
|
|
1923
|
+
|
|
1924
|
+
#### `scopeTokens`
|
|
1925
|
+
|
|
1926
|
+
```ts
|
|
1927
|
+
export function scopeTokens(scope: EvidenceScope | undefined): string[]
|
|
1928
|
+
```
|
|
1929
|
+
|
|
1930
|
+
The filter tokens one declaration states, normalized (letter case and spaces never matter): `surface:<value>`,
|
|
1931
|
+
`device:<value>`, `data:synthetic` or `data:real`, `dependencies:<value>`, `audio:recorded` or `audio:silent`,
|
|
1932
|
+
`pacing:<value>`. A field that is not declared adds no token. No scope at all is the single token
|
|
1933
|
+
{@link UNKNOWN_SCOPE}, so a filter can ask for the unknown on purpose; a scope that declares nothing readable is
|
|
1934
|
+
also unknown.
|
|
1935
|
+
|
|
1936
|
+
#### `matchesScope`
|
|
1937
|
+
|
|
1938
|
+
```ts
|
|
1939
|
+
export function matchesScope(tokens: readonly string[], wanted: readonly string[]): boolean
|
|
1940
|
+
```
|
|
1941
|
+
|
|
1942
|
+
Whether one recording's tokens satisfy a filter: every wanted token must be among them. Tokens compare normalized.
|
|
1943
|
+
An empty filter matches everything. The token {@link UNKNOWN_SCOPE} matches only a recording with no declared
|
|
1944
|
+
fields, so asking for `device:physical` never includes a recording that did not say what it ran on.
|
|
1945
|
+
|
|
1946
|
+
#### `ScopeBasis`
|
|
1947
|
+
|
|
1948
|
+
```ts
|
|
1949
|
+
export type ScopeBasis = 'verified' | 'declared' | 'undeclared';
|
|
1950
|
+
```
|
|
1951
|
+
|
|
1952
|
+
How a fact is known: `verified` (independently checked), `declared` (the producer's word) or `undeclared` (unknown).
|
|
1953
|
+
|
|
1954
|
+
#### `ScopeFact`
|
|
1955
|
+
|
|
1956
|
+
```ts
|
|
1957
|
+
export type ScopeFact = {
|
|
1958
|
+
readonly dimension: ScopeDimension;
|
|
1959
|
+
/** A name for the dimension: `Device`, `Audio`. */
|
|
1960
|
+
readonly label: string;
|
|
1961
|
+
/** The value as words: `Physical device`, `Silent`, `Not declared`. */
|
|
1962
|
+
readonly value: string;
|
|
1963
|
+
readonly basis: ScopeBasis;
|
|
1964
|
+
};
|
|
1965
|
+
```
|
|
1966
|
+
|
|
1967
|
+
One line of what a scope says: the dimension, a sentence for a person, and how well it is known.
|
|
1968
|
+
|
|
1969
|
+
#### `ScopeBadge`
|
|
1970
|
+
|
|
1971
|
+
```ts
|
|
1972
|
+
export type ScopeBadge = {
|
|
1973
|
+
readonly dimension: ScopeDimension | 'unknown';
|
|
1974
|
+
readonly text: string;
|
|
1975
|
+
readonly basis: ScopeBasis;
|
|
1976
|
+
};
|
|
1977
|
+
```
|
|
1978
|
+
|
|
1979
|
+
A short qualification to show beside a recording: `Device: Simulator`, `Scripted dependencies`, `Silent`.
|
|
1980
|
+
|
|
1981
|
+
#### `scopeFacts`
|
|
1982
|
+
|
|
1983
|
+
```ts
|
|
1984
|
+
export function scopeFacts(scope: EvidenceScope | undefined): ScopeFact[]
|
|
1985
|
+
```
|
|
1986
|
+
|
|
1987
|
+
Every dimension of a scope with its value and how it is known, in a fixed order, including the ones nobody declared
|
|
1988
|
+
(`Not declared`, basis `undeclared`). A dimension is `verified` only when the scope's `verified` names it and it has
|
|
1989
|
+
a value; everything else a producer wrote is `declared`. With no scope every dimension is undeclared.
|
|
1990
|
+
|
|
1991
|
+
#### `scopeBadges`
|
|
1992
|
+
|
|
1993
|
+
```ts
|
|
1994
|
+
export function scopeBadges(scope: EvidenceScope | undefined): ScopeBadge[]
|
|
1995
|
+
```
|
|
1996
|
+
|
|
1997
|
+
The concise badges for a scope: one per declared dimension, in a fixed order (`Surface: Browser`, `Device: Simulator`, `Synthetic
|
|
1998
|
+
data`, `Scripted dependencies`, `Silent`, `Paced`). With no declared dimension the one badge `Scope unknown`, so
|
|
1999
|
+
a reviewer is told nothing was claimed rather than shown nothing.
|
|
2000
|
+
|
|
2001
|
+
#### `scopeCaveats`
|
|
2002
|
+
|
|
2003
|
+
```ts
|
|
2004
|
+
export function scopeCaveats(scope: EvidenceScope | undefined): string[]
|
|
2005
|
+
```
|
|
2006
|
+
|
|
2007
|
+
What a reviewer must not read into evidence: a sentence per thing that is not shown. It names the assertions the
|
|
2008
|
+
result stands on when there are some (so activity visible beyond them is not verified), and every kind of reality
|
|
2009
|
+
that was not declared. Empty only for a scope that declares and verifies everything and lists no limitation.
|
|
2010
|
+
|
|
1847
2011
|
### Report evidence types
|
|
1848
2012
|
|
|
1849
2013
|
Source: `packages/reports/src/evidence-types.ts`. Import: `@crvouga/atlas/reports/evidence-types`
|
|
@@ -1916,6 +2080,8 @@ export type VideoRef = {
|
|
|
1916
2080
|
readonly recording?: string;
|
|
1917
2081
|
/** False when the reference names no bytes at all (no bundle key, address or inline text), so it can never be played. */
|
|
1918
2082
|
readonly available: boolean;
|
|
2083
|
+
/** What this recording shows, as declared for it: its own `scope`, else its attempt's, check's, part's or layer's. Absent when none declared one: its scope is unknown, not live, physical or audible. */
|
|
2084
|
+
readonly scope?: EvidenceScope;
|
|
1919
2085
|
};
|
|
1920
2086
|
```
|
|
1921
2087
|
|
|
@@ -2419,7 +2585,7 @@ A number a check recorded. Exceeding `limit` is shown and never fails the check
|
|
|
2419
2585
|
#### `EvidenceExtras`
|
|
2420
2586
|
|
|
2421
2587
|
```ts
|
|
2422
|
-
export type EvidenceExtras = Partial<Pick<import('../../schema/src/layer').Check, 'tags' | 'labels' | 'links' | 'steps' | 'attachments' | 'annotations' | 'attempts' | 'extensions'>>;
|
|
2588
|
+
export type EvidenceExtras = Partial<Pick<import('../../schema/src/layer').Check, 'tags' | 'labels' | 'links' | 'steps' | 'attachments' | 'annotations' | 'attempts' | 'extensions' | 'scope'>>;
|
|
2423
2589
|
```
|
|
2424
2590
|
|
|
2425
2591
|
What a check may carry beyond its outcome. All optional and open: see docs/extending.md.
|
|
@@ -4333,7 +4499,7 @@ String choices (such as `{ device: 'iphone-15' }`) that make a result its own sl
|
|
|
4333
4499
|
#### `LayerExtras`
|
|
4334
4500
|
|
|
4335
4501
|
```ts
|
|
4336
|
-
export type LayerExtras = Partial<Pick<import('../../schema/src/layer').LayerDocument, 'tags' | 'labels' | 'links' | 'attachments' | 'annotations' | 'extensions' | 'recordings'>>;
|
|
4502
|
+
export type LayerExtras = Partial<Pick<import('../../schema/src/layer').LayerDocument, 'tags' | 'labels' | 'links' | 'attachments' | 'annotations' | 'extensions' | 'recordings' | 'scope'>>;
|
|
4337
4503
|
```
|
|
4338
4504
|
|
|
4339
4505
|
What a layer may carry as a whole. All optional and open: see docs/extending.md.
|
|
@@ -6636,7 +6802,7 @@ See docs/server.md#streaming-a-run-as-it-happens
|
|
|
6636
6802
|
#### `ReporterIdentity`
|
|
6637
6803
|
|
|
6638
6804
|
```ts
|
|
6639
|
-
export type ReporterIdentity = Pick<PublishChecksInput, 'producer' | 'kind' | 'title' | 'sha' | 'branch' | 'runUrl' | 'run' | 'environment' | 'parameters' | 'freshness' | 'specs' | 'tags' | 'labels' | 'links' | 'attachments' | 'annotations' | 'extensions'>;
|
|
6805
|
+
export type ReporterIdentity = Pick<PublishChecksInput, 'producer' | 'kind' | 'title' | 'sha' | 'branch' | 'runUrl' | 'run' | 'environment' | 'parameters' | 'freshness' | 'specs' | 'tags' | 'labels' | 'links' | 'attachments' | 'annotations' | 'scope' | 'extensions'>;
|
|
6640
6806
|
```
|
|
6641
6807
|
|
|
6642
6808
|
What every layer of one stream shares: who is reporting, from where. Whatever `publishChecks` takes, except the checks and the time.
|
|
@@ -8217,6 +8383,8 @@ export type TestSummary = {
|
|
|
8217
8383
|
readonly artifactTags?: readonly string[];
|
|
8218
8384
|
/** The normalized tags its attachments inherit from their check and run (those of the results that have attachments), apart from `artifactTags`. What an inherited artifact query also matches. */
|
|
8219
8385
|
readonly inheritedArtifactTags?: readonly string[];
|
|
8386
|
+
/** What each distinct playable recording of its current results declares it shows, as filter tokens (`device:physical`, `audio:silent`; see `scopeTokens`). A recording that declared nothing is `['unknown']`. Absent when it has no playable recording. A query's `scope` must hold for one recording, so evidence from two different devices never combines. */
|
|
8387
|
+
readonly scopeProfiles?: readonly (readonly string[])[];
|
|
8220
8388
|
readonly labels: Readonly<Record<string, string>>;
|
|
8221
8389
|
/** What its current results carry: attachments, steps, retries, and the kinds of attachment. */
|
|
8222
8390
|
readonly evidence: { readonly attachments: number; readonly steps: number; readonly attempts: number; readonly roles: readonly string[] };
|
|
@@ -8309,6 +8477,8 @@ export type CatalogFacets = {
|
|
|
8309
8477
|
readonly tags: readonly FacetValue[];
|
|
8310
8478
|
/** The tags of attachments, normalized. Absent in documents of older servers. */
|
|
8311
8479
|
readonly artifactTags?: readonly FacetValue[];
|
|
8480
|
+
/** The scope tokens of its recordings (`device:simulator`, `audio:recorded`, `unknown`), with how many tests have a recording that states each. Absent in documents of older servers. */
|
|
8481
|
+
readonly scopes?: readonly FacetValue[];
|
|
8312
8482
|
readonly branches: readonly FacetValue[];
|
|
8313
8483
|
readonly labels: Readonly<Record<string, readonly FacetValue[]>>;
|
|
8314
8484
|
};
|
|
@@ -8350,6 +8520,8 @@ export type TestQuery = {
|
|
|
8350
8520
|
readonly artifactTag?: readonly string[];
|
|
8351
8521
|
/** With `artifactTag`: also match the tags an attachment inherits from its check and run (a recording without a tag of its own under a run tagged `demo`). Default false: the exact query. */
|
|
8352
8522
|
readonly inherited?: boolean;
|
|
8523
|
+
/** Only tests with one playable recording that states every one of these (`device:physical`, `audio:recorded`, `data:synthetic`, `dependencies:live`, `surface:browser`, `pacing:paced`; see `scopeTokens`). Evidence that did not declare a value never matches it; `unknown` matches only a recording that declared no scope. */
|
|
8524
|
+
readonly scope?: readonly string[];
|
|
8353
8525
|
/** Only tests recorded in this run (a key from `runList`). */
|
|
8354
8526
|
readonly run?: string;
|
|
8355
8527
|
readonly sort?: TestSort;
|
|
@@ -8762,7 +8934,7 @@ One run with every result it recorded, or null when no run has that key.
|
|
|
8762
8934
|
export function parseTestQuery(params: URLSearchParams): TestQuery
|
|
8763
8935
|
```
|
|
8764
8936
|
|
|
8765
|
-
Reads a `TestQuery` from URL search parameters: `q`, `sort`, `offset`, `limit`, `run`, and repeatable or comma-separated `status`, `environment`, `producer`, `domain`, `spec`, `tag`, `artifactTag`, and `inherited=1`.
|
|
8937
|
+
Reads a `TestQuery` from URL search parameters: `q`, `sort`, `offset`, `limit`, `run`, and repeatable or comma-separated `status`, `environment`, `producer`, `domain`, `spec`, `tag`, `artifactTag`, `scope` (every token must hold for one recording), and `inherited=1`.
|
|
8766
8938
|
|
|
8767
8939
|
#### `formatMs`
|
|
8768
8940
|
|
package/docs/reference/schema.md
CHANGED
|
@@ -776,6 +776,29 @@ export const MomentSchema = …
|
|
|
776
776
|
|
|
777
777
|
Where in a check something happened: at a step, and/or this long after the check started.
|
|
778
778
|
|
|
779
|
+
#### `EvidenceScopeSchema`
|
|
780
|
+
|
|
781
|
+
```ts
|
|
782
|
+
export const EvidenceScopeSchema = …
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
What a piece of evidence does and does not show, declared by the producer that made it: where it ran, whether its
|
|
786
|
+
data and dependencies were real, whether audio was captured, how it was paced and which assertions the result
|
|
787
|
+
stands on. Every field is optional and a field that is absent is unknown, never assumed live, physical or end to end.
|
|
788
|
+
|
|
789
|
+
It is a declaration, not a finding: Atlas shows it as the producer's word, apart from the dimensions the producer
|
|
790
|
+
names in `verified` (facts something independent checked). It describes evidence and never decides a status, a slot
|
|
791
|
+
or a winner. The nearest declaration wins as a whole, never field by field: an attachment's `scope`, else its
|
|
792
|
+
attempt's, else its check's, else its part's, else its layer's.
|
|
793
|
+
|
|
794
|
+
#### `EvidenceScope`
|
|
795
|
+
|
|
796
|
+
```ts
|
|
797
|
+
export type EvidenceScope = z.output<typeof EvidenceScopeSchema>;
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
A producer-declared evidence scope as stored in a layer, parsed from `EvidenceScopeSchema`.
|
|
801
|
+
|
|
779
802
|
#### `AttachmentSchema`
|
|
780
803
|
|
|
781
804
|
```ts
|
package/docs/rich-evidence.md
CHANGED
|
@@ -27,6 +27,7 @@ decided exactly as before ([the report contract](report-contract.md)). Evidence
|
|
|
27
27
|
| `attempts` | check | Every try of a retried check, with its own status, detail, steps and attachments. A check that passed after failing is flaky. |
|
|
28
28
|
| `tags`, `labels` | check, layer | Filters and groups (`smoke`, `flaky`, an owner, a browser). |
|
|
29
29
|
| `tags` | attachment | Marks one recording or screenshot, for example `demo`: shown as a `#demo` chip, and searchable. See [Tagging artifacts and demos](#tagging-artifacts-and-demos). |
|
|
30
|
+
| `scope` | attachment, attempt, check, part, layer | What a recording does and does not show: where it ran, whether data and dependencies were real, whether audio was captured. See [Evidence scope](#evidence-scope). |
|
|
30
31
|
| `annotations`, `links` | check, step, attempt, layer | Notes, warnings and issue links next to the result. |
|
|
31
32
|
| `specPath` | check | The spec a check belongs to (a `.feature`, a test file), relative to the repository. Checks are grouped by it. |
|
|
32
33
|
| `specs[]` (`path`, `domain`, `kind`, `title`) | layer | Names each spec and its group (`domain`), so a server can group checks by spec with a title even when no [spec set](integrating.md#tell-atlas-which-spec-a-check-belongs-to) has been published. |
|
|
@@ -62,6 +63,52 @@ attachments with that tag, and says how many match. Tags describe evidence; they
|
|
|
62
63
|
- **Older layers.** A layer without attachment tags reads exactly as before. Tags on an attachment that cannot be read
|
|
63
64
|
(not a list of strings) are dropped, and the attachment, check and layer stay.
|
|
64
65
|
|
|
66
|
+
## Evidence scope
|
|
67
|
+
|
|
68
|
+
A green result and a video do not say whether the speech was real, the replies scripted, the audio recorded, the device
|
|
69
|
+
physical or a vendor substituted. Declare it where the evidence is, in an optional `scope`, and the viewer shows it as
|
|
70
|
+
short badges above the recording (before it is played), with the exact declaration on demand:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
{
|
|
74
|
+
name: 'checkout.webm', role: 'video',
|
|
75
|
+
scope: {
|
|
76
|
+
surface: 'browser', // where the product ran: browser, native-app, api…
|
|
77
|
+
deviceKind: 'simulator', // simulator, emulator, physical, virtual-machine, desktop…
|
|
78
|
+
syntheticData: true, // made-up data (false: real, absent: unknown)
|
|
79
|
+
dependencies: 'scripted', // scripted, live or mixed
|
|
80
|
+
audioCaptured: false, // false: silent, absent: unknown
|
|
81
|
+
pacing: 'paced', // paced, real-time
|
|
82
|
+
asserted: ['the receipt is shown'], // what the result stands on
|
|
83
|
+
limitations: ['Replies are scripted'],
|
|
84
|
+
declaredBy: 'checkout e2e suite',
|
|
85
|
+
verified: ['deviceKind'], // fields something independent checked
|
|
86
|
+
verifiedBy: 'device farm session log',
|
|
87
|
+
provenance: [{ url: 'https://farm.example/session/1', name: 'Device session' }]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Every field is optional, and every value except the booleans is an open string.
|
|
93
|
+
|
|
94
|
+
- **Unknown is not live.** A recording with no scope, or a scope that does not say a thing, shows `Scope unknown` or
|
|
95
|
+
`Not declared`. It is never assumed live, physical, audible or end to end.
|
|
96
|
+
- **Declared is not verified.** Atlas shows `scope` as the producer's word. Only the fields named in `verified` are
|
|
97
|
+
marked verified (with `verifiedBy`), and `provenance` links where to check them. Atlas does not check any of it.
|
|
98
|
+
- **Only what was asserted.** `asserted` lists the boundaries the result stands on. The viewer says that other activity
|
|
99
|
+
visible in the clip is not verified behavior; with no `asserted` it says that no boundary was declared.
|
|
100
|
+
- **The nearest declaration, whole.** A recording takes the `scope` of the attachment, else its attempt, its check, its
|
|
101
|
+
part, its layer, in that order. It is never merged field by field, so a retry on a physical phone does not borrow
|
|
102
|
+
the limitations declared for the simulator try, and the badges change with the exact recording opened.
|
|
103
|
+
- **Filtering.** `GET /api/reports/tests?scope=device:physical,audio:recorded` (and the explorer's *Recording scope*
|
|
104
|
+
filter) lists tests with one playable recording that states every token: `surface:<v>`, `device:<v>`,
|
|
105
|
+
`data:synthetic` or `data:real`, `dependencies:<v>`, `audio:recorded` or `audio:silent`, `pacing:<v>`. A simulator
|
|
106
|
+
and a physical phone are separate values. Evidence that did not declare a value is never included, and `unknown`
|
|
107
|
+
asks for recordings that declared nothing. Each test lists its recordings' tokens as `scopeProfiles`, the facets as
|
|
108
|
+
`scopes`, and the search box finds the words in them.
|
|
109
|
+
- **It decides nothing.** A scope never changes a status, a slot or a winner. A scope an older Atlas cannot read is
|
|
110
|
+
dropped, and the attachment, check and layer stay.
|
|
111
|
+
|
|
65
112
|
## Finding runs and artifacts
|
|
66
113
|
|
|
67
114
|
The viewer's **Runs** and **Artifacts** tabs and the API answer one query, so a link means the same thing everywhere.
|
package/llms-full.txt
CHANGED
|
@@ -1014,6 +1014,7 @@ decided exactly as before ([the report contract](report-contract.md)). Evidence
|
|
|
1014
1014
|
| `attempts` | check | Every try of a retried check, with its own status, detail, steps and attachments. A check that passed after failing is flaky. |
|
|
1015
1015
|
| `tags`, `labels` | check, layer | Filters and groups (`smoke`, `flaky`, an owner, a browser). |
|
|
1016
1016
|
| `tags` | attachment | Marks one recording or screenshot, for example `demo`: shown as a `#demo` chip, and searchable. See [Tagging artifacts and demos](#tagging-artifacts-and-demos). |
|
|
1017
|
+
| `scope` | attachment, attempt, check, part, layer | What a recording does and does not show: where it ran, whether data and dependencies were real, whether audio was captured. See [Evidence scope](#evidence-scope). |
|
|
1017
1018
|
| `annotations`, `links` | check, step, attempt, layer | Notes, warnings and issue links next to the result. |
|
|
1018
1019
|
| `specPath` | check | The spec a check belongs to (a `.feature`, a test file), relative to the repository. Checks are grouped by it. |
|
|
1019
1020
|
| `specs[]` (`path`, `domain`, `kind`, `title`) | layer | Names each spec and its group (`domain`), so a server can group checks by spec with a title even when no [spec set](integrating.md#tell-atlas-which-spec-a-check-belongs-to) has been published. |
|
|
@@ -1049,6 +1050,52 @@ attachments with that tag, and says how many match. Tags describe evidence; they
|
|
|
1049
1050
|
- **Older layers.** A layer without attachment tags reads exactly as before. Tags on an attachment that cannot be read
|
|
1050
1051
|
(not a list of strings) are dropped, and the attachment, check and layer stay.
|
|
1051
1052
|
|
|
1053
|
+
## Evidence scope
|
|
1054
|
+
|
|
1055
|
+
A green result and a video do not say whether the speech was real, the replies scripted, the audio recorded, the device
|
|
1056
|
+
physical or a vendor substituted. Declare it where the evidence is, in an optional `scope`, and the viewer shows it as
|
|
1057
|
+
short badges above the recording (before it is played), with the exact declaration on demand:
|
|
1058
|
+
|
|
1059
|
+
```ts
|
|
1060
|
+
{
|
|
1061
|
+
name: 'checkout.webm', role: 'video',
|
|
1062
|
+
scope: {
|
|
1063
|
+
surface: 'browser', // where the product ran: browser, native-app, api…
|
|
1064
|
+
deviceKind: 'simulator', // simulator, emulator, physical, virtual-machine, desktop…
|
|
1065
|
+
syntheticData: true, // made-up data (false: real, absent: unknown)
|
|
1066
|
+
dependencies: 'scripted', // scripted, live or mixed
|
|
1067
|
+
audioCaptured: false, // false: silent, absent: unknown
|
|
1068
|
+
pacing: 'paced', // paced, real-time
|
|
1069
|
+
asserted: ['the receipt is shown'], // what the result stands on
|
|
1070
|
+
limitations: ['Replies are scripted'],
|
|
1071
|
+
declaredBy: 'checkout e2e suite',
|
|
1072
|
+
verified: ['deviceKind'], // fields something independent checked
|
|
1073
|
+
verifiedBy: 'device farm session log',
|
|
1074
|
+
provenance: [{ url: 'https://farm.example/session/1', name: 'Device session' }]
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1079
|
+
Every field is optional, and every value except the booleans is an open string.
|
|
1080
|
+
|
|
1081
|
+
- **Unknown is not live.** A recording with no scope, or a scope that does not say a thing, shows `Scope unknown` or
|
|
1082
|
+
`Not declared`. It is never assumed live, physical, audible or end to end.
|
|
1083
|
+
- **Declared is not verified.** Atlas shows `scope` as the producer's word. Only the fields named in `verified` are
|
|
1084
|
+
marked verified (with `verifiedBy`), and `provenance` links where to check them. Atlas does not check any of it.
|
|
1085
|
+
- **Only what was asserted.** `asserted` lists the boundaries the result stands on. The viewer says that other activity
|
|
1086
|
+
visible in the clip is not verified behavior; with no `asserted` it says that no boundary was declared.
|
|
1087
|
+
- **The nearest declaration, whole.** A recording takes the `scope` of the attachment, else its attempt, its check, its
|
|
1088
|
+
part, its layer, in that order. It is never merged field by field, so a retry on a physical phone does not borrow
|
|
1089
|
+
the limitations declared for the simulator try, and the badges change with the exact recording opened.
|
|
1090
|
+
- **Filtering.** `GET /api/reports/tests?scope=device:physical,audio:recorded` (and the explorer's *Recording scope*
|
|
1091
|
+
filter) lists tests with one playable recording that states every token: `surface:<v>`, `device:<v>`,
|
|
1092
|
+
`data:synthetic` or `data:real`, `dependencies:<v>`, `audio:recorded` or `audio:silent`, `pacing:<v>`. A simulator
|
|
1093
|
+
and a physical phone are separate values. Evidence that did not declare a value is never included, and `unknown`
|
|
1094
|
+
asks for recordings that declared nothing. Each test lists its recordings' tokens as `scopeProfiles`, the facets as
|
|
1095
|
+
`scopes`, and the search box finds the words in them.
|
|
1096
|
+
- **It decides nothing.** A scope never changes a status, a slot or a winner. A scope an older Atlas cannot read is
|
|
1097
|
+
dropped, and the attachment, check and layer stay.
|
|
1098
|
+
|
|
1052
1099
|
## Finding runs and artifacts
|
|
1053
1100
|
|
|
1054
1101
|
The viewer's **Runs** and **Artifacts** tabs and the API answer one query, so a link means the same thing everywhere.
|
|
@@ -2757,6 +2804,7 @@ carry any of these. Every one is optional.
|
|
|
2757
2804
|
| `attempts[]` | Every try, when the check was retried. |
|
|
2758
2805
|
| `tags[]` | Free labels to filter by: `smoke`, `payments`. |
|
|
2759
2806
|
| `labels{}` | Facts about the result that are not its identity: `owner`, `severity`, `browser`, `build`. |
|
|
2807
|
+
| `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). |
|
|
2760
2808
|
| `links[]` | Where to read more: the CI run, the issue, the trace client. |
|
|
2761
2809
|
| `extensions{}` | Structured data of your own or a tool's, under a name you own. |
|
|
2762
2810
|
| `measurements[]` | Numbers, with units and an allowance (already part of the contract). |
|
|
@@ -4923,6 +4971,7 @@ Send `format=markdown` (or `Accept: text/markdown`) for the same as a Markdown r
|
|
|
4923
4971
|
- `tag` (query): Only tests with this tag. Repeatable or comma-separated.
|
|
4924
4972
|
- `artifactTag` (query): Only tests with an attachment tagged this (`demo` and `#demo` are the same tag), not counting tags inherited from a check or run: the exact artifact query. `inherited=1` widens it to the tags inherited from the check and run. Repeatable or comma-separated.
|
|
4925
4973
|
- `inherited` (query): With `artifactTag`: `1` also matches the tags an attachment inherits from its check and run, so a recording without a tag of its own under a run tagged `demo` is found. Without it the query is exact.
|
|
4974
|
+
- `scope` (query): Only tests with one playable recording that states every one of these producer-declared qualifications: `surface:<value>`, `device:<value>` (`device:physical` and `device:simulator` stay apart), `data:synthetic` or `data:real`, `dependencies:<value>`, `audio:recorded` or `audio:silent`, `pacing:<value>`. All must hold for the same recording. A recording that did not declare a value never matches it, and `unknown` matches only a recording that declared no scope. Repeatable or comma-separated.
|
|
4926
4975
|
- `run` (query): Only tests recorded in this run (a `key` from `/api/reports/runs`).
|
|
4927
4976
|
- `sort` (query): The order: `spec` (default: domain, spec, title), `title`, `recent`, `slowest`, `failures`, `flaky` or `runs`.
|
|
4928
4977
|
- `offset` (query): Skip this many matching tests.
|
|
@@ -4954,6 +5003,7 @@ Scoped to one project. The same project may be selected on the unprefixed API us
|
|
|
4954
5003
|
- `tag` (query): Only tests with this tag. Repeatable or comma-separated.
|
|
4955
5004
|
- `artifactTag` (query): Only tests with an attachment tagged this (`demo` and `#demo` are the same tag), not counting tags inherited from a check or run: the exact artifact query. `inherited=1` widens it to the tags inherited from the check and run. Repeatable or comma-separated.
|
|
4956
5005
|
- `inherited` (query): With `artifactTag`: `1` also matches the tags an attachment inherits from its check and run, so a recording without a tag of its own under a run tagged `demo` is found. Without it the query is exact.
|
|
5006
|
+
- `scope` (query): Only tests with one playable recording that states every one of these producer-declared qualifications: `surface:<value>`, `device:<value>` (`device:physical` and `device:simulator` stay apart), `data:synthetic` or `data:real`, `dependencies:<value>`, `audio:recorded` or `audio:silent`, `pacing:<value>`. All must hold for the same recording. A recording that did not declare a value never matches it, and `unknown` matches only a recording that declared no scope. Repeatable or comma-separated.
|
|
4957
5007
|
- `run` (query): Only tests recorded in this run (a `key` from `/api/reports/runs`).
|
|
4958
5008
|
- `sort` (query): The order: `spec` (default: domain, spec, title), `title`, `recent`, `slowest`, `failures`, `flaky` or `runs`.
|
|
4959
5009
|
- `offset` (query): Skip this many matching tests.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crvouga/atlas",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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",
|