@crvouga/atlas 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/reports/src/evidence-detail.d.ts +37 -3
- package/dist/reports/src/evidence-detail.js +52 -20
- 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/export-evidence.js +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/layers-route.d.ts +2 -1
- package/dist/reports/src/layers-route.js +8 -4
- 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 +34 -1
- package/dist/reports/src/test-catalog.js +23 -5
- package/dist/schema/src/api.d.ts +542 -0
- package/dist/schema/src/api.js +17 -5
- package/dist/schema/src/layer.d.ts +981 -0
- package/dist/schema/src/layer.js +43 -4
- package/dist/server/src/openapi.js +3 -2
- package/docs/extending.md +1 -0
- package/docs/reference/http-api.md +6 -4
- package/docs/reference/reports.md +243 -14
- package/docs/reference/schema.md +23 -0
- package/docs/rich-evidence.md +70 -0
- package/docs/ui.md +1 -1
- package/llms-full.txt +78 -5
- package/package.json +1 -1
- package/packages/schema/schemas/check-detail.schema.json +697 -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 +181 -0
- package/packages/schema/schemas/report-runs.schema.json +6 -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 +2721 -48
- package/packages/visualizer/src/evidence/artifacts.tsx +77 -29
- 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 +28 -5
- package/packages/visualizer/src/explorer/RunArtifacts.tsx +246 -0
- package/packages/visualizer/src/explorer/RunsView.tsx +40 -9
- package/packages/visualizer/src/explorer/search.ts +10 -3
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 } },
|
|
@@ -392,7 +393,7 @@ const BASE_OPERATIONS = [
|
|
|
392
393
|
{
|
|
393
394
|
route: 'GET /api/reports/runs/detail', path: '/api/reports/runs/detail', operationId: 'getReportRun', tag: 'Report', role: 'read',
|
|
394
395
|
summary: 'Every test one run recorded',
|
|
395
|
-
description: 'One run and its last result for every test, environment and producer it recorded, failures first. Send `format=markdown` for Markdown.',
|
|
396
|
+
description: 'One run and its last result for every test, environment and producer it recorded, failures first, and `layers`: each layer of the run with its backend, how many checks it recorded (zero means evidence only, never a pass) and how many files it carries as a whole. Open a layer\'s files with `/api/reports/evidence/detail/{backend}/{id}`. Send `format=markdown` for Markdown.',
|
|
396
397
|
parameters: [
|
|
397
398
|
{ name: 'key', in: 'query', required: true, description: 'The run\'s `key` from `/api/reports/runs`, URL-encoded.', schema: str },
|
|
398
399
|
{ name: 'format', in: 'query', description: '`markdown` for Markdown instead of JSON.', schema: { type: 'string', enum: ['json', 'markdown'] } }
|
|
@@ -429,7 +430,7 @@ const BASE_OPERATIONS = [
|
|
|
429
430
|
{
|
|
430
431
|
route: 'GET /api/reports/evidence/detail/:backend/:id', path: '/api/reports/evidence/detail/{backend}/{id}', operationId: 'getCheckDetail', tag: 'Report', role: 'read',
|
|
431
432
|
summary: 'Everything one result carries',
|
|
432
|
-
description: 'The timeline, annotations, retries, attachments and extensions of one check, and what its run carries as a whole. Without `check`, only the run\'s. Attachment bytes are fetched from `/api/reports/layers/{id}/blobs/{bundleKey}`.',
|
|
433
|
+
description: 'The timeline, annotations, retries, attachments and extensions of one check, and what its run carries as a whole. Without `check`, only the run\'s, and `parts`: what each part of the layer that carries files or links of its own holds. Attachment bytes are fetched from `/api/reports/layers/{id}/blobs/{bundleKey}`.',
|
|
433
434
|
parameters: [BACKEND, LAYER_ID, { name: 'check', in: 'query', description: 'The check id.', schema: str }, { name: 'kind', in: 'query', description: 'The kind of the part, when the same id is in several.', schema: str }],
|
|
434
435
|
responses: { '200': json('The detail.', 'CheckDetail'), '401': UNAUTHORIZED, '404': problem('No such layer or check.') }
|
|
435
436
|
},
|
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.
|
|
@@ -754,7 +756,7 @@ Responses: `200` The matching results and the facets.; `304` Unchanged since the
|
|
|
754
756
|
|
|
755
757
|
`GET /api/reports/runs/detail` · role: read
|
|
756
758
|
|
|
757
|
-
One run and its last result for every test, environment and producer it recorded, failures first. Send `format=markdown` for Markdown.
|
|
759
|
+
One run and its last result for every test, environment and producer it recorded, failures first, and `layers`: each layer of the run with its backend, how many checks it recorded (zero means evidence only, never a pass) and how many files it carries as a whole. Open a layer's files with `/api/reports/evidence/detail/{backend}/{id}`. Send `format=markdown` for Markdown.
|
|
758
760
|
|
|
759
761
|
- `key` (query, required): The run's `key` from `/api/reports/runs`, URL-encoded.
|
|
760
762
|
- `format` (query): `markdown` for Markdown instead of JSON.
|
|
@@ -766,7 +768,7 @@ Responses: `200` The run and its results.; `304` Unchanged since the `If-None-Ma
|
|
|
766
768
|
|
|
767
769
|
`GET /api/projects/{project}/reports/runs/detail` · role: read
|
|
768
770
|
|
|
769
|
-
One run and its last result for every test, environment and producer it recorded, failures first. Send `format=markdown` for Markdown.
|
|
771
|
+
One run and its last result for every test, environment and producer it recorded, failures first, and `layers`: each layer of the run with its backend, how many checks it recorded (zero means evidence only, never a pass) and how many files it carries as a whole. Open a layer's files with `/api/reports/evidence/detail/{backend}/{id}`. Send `format=markdown` for Markdown.
|
|
770
772
|
|
|
771
773
|
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.
|
|
772
774
|
|
|
@@ -876,7 +878,7 @@ Responses: `200` The rows.; `401` No token, or an unknown one. Send `Authorizati
|
|
|
876
878
|
|
|
877
879
|
`GET /api/reports/evidence/detail/{backend}/{id}` · role: read
|
|
878
880
|
|
|
879
|
-
The timeline, annotations, retries, attachments and extensions of one check, and what its run carries as a whole. Without `check`, only the run's. Attachment bytes are fetched from `/api/reports/layers/{id}/blobs/{bundleKey}`.
|
|
881
|
+
The timeline, annotations, retries, attachments and extensions of one check, and what its run carries as a whole. Without `check`, only the run's, and `parts`: what each part of the layer that carries files or links of its own holds. Attachment bytes are fetched from `/api/reports/layers/{id}/blobs/{bundleKey}`.
|
|
880
882
|
|
|
881
883
|
- `backend` (path, required): A backend id from `/api/server`.
|
|
882
884
|
- `id` (path, required): A layer id: 64 lowercase hex digits.
|
|
@@ -890,7 +892,7 @@ Responses: `200` The detail.; `401` No token, or an unknown one. Send `Authoriza
|
|
|
890
892
|
|
|
891
893
|
`GET /api/projects/{project}/reports/evidence/detail/{backend}/{id}` · role: read
|
|
892
894
|
|
|
893
|
-
The timeline, annotations, retries, attachments and extensions of one check, and what its run carries as a whole. Without `check`, only the run's. Attachment bytes are fetched from `/api/reports/layers/{id}/blobs/{bundleKey}`.
|
|
895
|
+
The timeline, annotations, retries, attachments and extensions of one check, and what its run carries as a whole. Without `check`, only the run's, and `parts`: what each part of the layer that carries files or links of its own holds. Attachment bytes are fetched from `/api/reports/layers/{id}/blobs/{bundleKey}`.
|
|
894
896
|
|
|
895
897
|
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.
|
|
896
898
|
|
|
@@ -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
|
|
|
@@ -1701,6 +1713,16 @@ Whether an attachment is a video, by its effective media type: the producer's `c
|
|
|
1701
1713
|
name implies. A role never decides this on its own (`demo` on a PNG is not a video), except that a `video` role
|
|
1702
1714
|
settles a file whose type nothing says (`application/octet-stream`).
|
|
1703
1715
|
|
|
1716
|
+
#### `attachmentIdentity`
|
|
1717
|
+
|
|
1718
|
+
```ts
|
|
1719
|
+
export function attachmentIdentity(item: Pick<Attachment, 'name' | 'sha256' | 'bundleKey' | 'uri' | 'text'>): string | undefined
|
|
1720
|
+
```
|
|
1721
|
+
|
|
1722
|
+
What identifies an attachment's bytes: its digest, else its bundle key, address or inline text. Attachments with the
|
|
1723
|
+
same identity are the same file however many results reference them. Undefined when it names no bytes at all (it can
|
|
1724
|
+
never be opened). This is the `recording` of a {@link VideoRef}.
|
|
1725
|
+
|
|
1704
1726
|
#### `videoCounts`
|
|
1705
1727
|
|
|
1706
1728
|
```ts
|
|
@@ -1730,6 +1752,8 @@ export type CheckDetail = {
|
|
|
1730
1752
|
readonly check: Check;
|
|
1731
1753
|
/** What the run as a whole carries: its logs, its links, its labels. */
|
|
1732
1754
|
readonly run: EvidenceHolder;
|
|
1755
|
+
/** 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. */
|
|
1756
|
+
readonly inheritedScope?: EvidenceScope;
|
|
1733
1757
|
};
|
|
1734
1758
|
```
|
|
1735
1759
|
|
|
@@ -1738,7 +1762,7 @@ All of one result's evidence, read when someone opens it.
|
|
|
1738
1762
|
#### `checkDetailOf`
|
|
1739
1763
|
|
|
1740
1764
|
```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
|
|
1765
|
+
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
1766
|
```
|
|
1743
1767
|
|
|
1744
1768
|
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 +1773,28 @@ One result's evidence, from a layer: the first check with id `checkId` (in a par
|
|
|
1749
1773
|
export function runEvidence(layer: EvidenceHolder): EvidenceHolder
|
|
1750
1774
|
```
|
|
1751
1775
|
|
|
1752
|
-
What a layer carries as a whole (its tags, labels, links, attachments, annotations and extensions; not its parts' checks). Absent fields stay absent.
|
|
1776
|
+
What a layer carries as a whole (its scope, tags, labels, links, attachments, annotations and extensions; not its parts' checks). Absent fields stay absent.
|
|
1777
|
+
|
|
1778
|
+
#### `PartEvidence`
|
|
1779
|
+
|
|
1780
|
+
```ts
|
|
1781
|
+
export type PartEvidence = EvidenceHolder & {
|
|
1782
|
+
readonly kind: string;
|
|
1783
|
+
readonly id: string;
|
|
1784
|
+
readonly title: string;
|
|
1785
|
+
};
|
|
1786
|
+
```
|
|
1787
|
+
|
|
1788
|
+
What one part of a layer (one kind of report) carries as a whole, named by the part.
|
|
1789
|
+
|
|
1790
|
+
#### `partsEvidence`
|
|
1791
|
+
|
|
1792
|
+
```ts
|
|
1793
|
+
export function partsEvidence(layer: { readonly parts: readonly (EvidenceHolder & { readonly kind: string; readonly id: string; readonly title: string })[] }): PartEvidence[]
|
|
1794
|
+
```
|
|
1795
|
+
|
|
1796
|
+
The parts of a layer that carry evidence of their own (attachments, links, labels…), each with its name. Parts
|
|
1797
|
+
that carry none are left out. A part's files belong to the run, not to any one check.
|
|
1753
1798
|
|
|
1754
1799
|
#### `bundleKeysOf`
|
|
1755
1800
|
|
|
@@ -1844,6 +1889,156 @@ the next pass; `refresh` does not throw for those.
|
|
|
1844
1889
|
|
|
1845
1890
|
- `args.backend`: The backend id stamped on live layers.
|
|
1846
1891
|
|
|
1892
|
+
### Evidence scope
|
|
1893
|
+
|
|
1894
|
+
Source: `packages/reports/src/evidence-scope.ts`. Import: `@crvouga/atlas/reports/evidence-scope`
|
|
1895
|
+
|
|
1896
|
+
Reads what a producer declared a piece of evidence shows (`EvidenceScope`): which declaration applies to an
|
|
1897
|
+
attachment, the short badges a reviewer sees before playing a recording, the facts split into declared and
|
|
1898
|
+
independently verified, and the filter tokens (`device:physical`, `audio:recorded`) a query narrows by. Pure
|
|
1899
|
+
functions, safe in a browser.
|
|
1900
|
+
|
|
1901
|
+
The one rule: what is not declared is unknown. A missing scope, or a missing field, never reads as live, physical,
|
|
1902
|
+
audible or end to end, and a filter for a value never matches evidence that did not say it. A scope describes
|
|
1903
|
+
evidence; it never decides a status, a slot or a winner.
|
|
1904
|
+
|
|
1905
|
+
See docs/rich-evidence.md#evidence-scope
|
|
1906
|
+
|
|
1907
|
+
#### `SCOPE_DIMENSIONS`
|
|
1908
|
+
|
|
1909
|
+
```ts
|
|
1910
|
+
export const SCOPE_DIMENSIONS = …
|
|
1911
|
+
```
|
|
1912
|
+
|
|
1913
|
+
The fields of a scope that say something about the evidence and that `verified` may name, in the order they are shown.
|
|
1914
|
+
|
|
1915
|
+
#### `ScopeDimension`
|
|
1916
|
+
|
|
1917
|
+
```ts
|
|
1918
|
+
export type ScopeDimension = (typeof SCOPE_DIMENSIONS)[number];
|
|
1919
|
+
```
|
|
1920
|
+
|
|
1921
|
+
One of {@link SCOPE_DIMENSIONS}.
|
|
1922
|
+
|
|
1923
|
+
#### `UNKNOWN_SCOPE`
|
|
1924
|
+
|
|
1925
|
+
```ts
|
|
1926
|
+
export const UNKNOWN_SCOPE = …
|
|
1927
|
+
```
|
|
1928
|
+
|
|
1929
|
+
The filter token of a recording that declares no scope at all.
|
|
1930
|
+
|
|
1931
|
+
#### `effectiveScope`
|
|
1932
|
+
|
|
1933
|
+
```ts
|
|
1934
|
+
export function effectiveScope(...candidates: readonly (EvidenceScope | undefined)[]): EvidenceScope | undefined
|
|
1935
|
+
```
|
|
1936
|
+
|
|
1937
|
+
The declaration that applies to something: the nearest one wins as a whole, never field by field, so a recording
|
|
1938
|
+
never shows a limitation or a device that was declared for another source. Pass the candidates nearest first
|
|
1939
|
+
(attachment, attempt, check, part, layer); absent ones are skipped. Undefined when none declares a scope.
|
|
1940
|
+
|
|
1941
|
+
**Example**
|
|
1942
|
+
|
|
1943
|
+
```ts
|
|
1944
|
+
effectiveScope(attachment.scope, attempt.scope, check.scope, layer.scope);
|
|
1945
|
+
```
|
|
1946
|
+
|
|
1947
|
+
#### `scopeKey`
|
|
1948
|
+
|
|
1949
|
+
```ts
|
|
1950
|
+
export function scopeKey(scope: EvidenceScope | undefined): string
|
|
1951
|
+
```
|
|
1952
|
+
|
|
1953
|
+
A stable string that is equal for equal declarations (key order never matters), to tell one scope from another.
|
|
1954
|
+
|
|
1955
|
+
#### `scopeTokens`
|
|
1956
|
+
|
|
1957
|
+
```ts
|
|
1958
|
+
export function scopeTokens(scope: EvidenceScope | undefined): string[]
|
|
1959
|
+
```
|
|
1960
|
+
|
|
1961
|
+
The filter tokens one declaration states, normalized (letter case and spaces never matter): `surface:<value>`,
|
|
1962
|
+
`device:<value>`, `data:synthetic` or `data:real`, `dependencies:<value>`, `audio:recorded` or `audio:silent`,
|
|
1963
|
+
`pacing:<value>`. A field that is not declared adds no token. No scope at all is the single token
|
|
1964
|
+
{@link UNKNOWN_SCOPE}, so a filter can ask for the unknown on purpose; a scope that declares nothing readable is
|
|
1965
|
+
also unknown.
|
|
1966
|
+
|
|
1967
|
+
#### `matchesScope`
|
|
1968
|
+
|
|
1969
|
+
```ts
|
|
1970
|
+
export function matchesScope(tokens: readonly string[], wanted: readonly string[]): boolean
|
|
1971
|
+
```
|
|
1972
|
+
|
|
1973
|
+
Whether one recording's tokens satisfy a filter: every wanted token must be among them. Tokens compare normalized.
|
|
1974
|
+
An empty filter matches everything. The token {@link UNKNOWN_SCOPE} matches only a recording with no declared
|
|
1975
|
+
fields, so asking for `device:physical` never includes a recording that did not say what it ran on.
|
|
1976
|
+
|
|
1977
|
+
#### `ScopeBasis`
|
|
1978
|
+
|
|
1979
|
+
```ts
|
|
1980
|
+
export type ScopeBasis = 'verified' | 'declared' | 'undeclared';
|
|
1981
|
+
```
|
|
1982
|
+
|
|
1983
|
+
How a fact is known: `verified` (independently checked), `declared` (the producer's word) or `undeclared` (unknown).
|
|
1984
|
+
|
|
1985
|
+
#### `ScopeFact`
|
|
1986
|
+
|
|
1987
|
+
```ts
|
|
1988
|
+
export type ScopeFact = {
|
|
1989
|
+
readonly dimension: ScopeDimension;
|
|
1990
|
+
/** A name for the dimension: `Device`, `Audio`. */
|
|
1991
|
+
readonly label: string;
|
|
1992
|
+
/** The value as words: `Physical device`, `Silent`, `Not declared`. */
|
|
1993
|
+
readonly value: string;
|
|
1994
|
+
readonly basis: ScopeBasis;
|
|
1995
|
+
};
|
|
1996
|
+
```
|
|
1997
|
+
|
|
1998
|
+
One line of what a scope says: the dimension, a sentence for a person, and how well it is known.
|
|
1999
|
+
|
|
2000
|
+
#### `ScopeBadge`
|
|
2001
|
+
|
|
2002
|
+
```ts
|
|
2003
|
+
export type ScopeBadge = {
|
|
2004
|
+
readonly dimension: ScopeDimension | 'unknown';
|
|
2005
|
+
readonly text: string;
|
|
2006
|
+
readonly basis: ScopeBasis;
|
|
2007
|
+
};
|
|
2008
|
+
```
|
|
2009
|
+
|
|
2010
|
+
A short qualification to show beside a recording: `Device: Simulator`, `Scripted dependencies`, `Silent`.
|
|
2011
|
+
|
|
2012
|
+
#### `scopeFacts`
|
|
2013
|
+
|
|
2014
|
+
```ts
|
|
2015
|
+
export function scopeFacts(scope: EvidenceScope | undefined): ScopeFact[]
|
|
2016
|
+
```
|
|
2017
|
+
|
|
2018
|
+
Every dimension of a scope with its value and how it is known, in a fixed order, including the ones nobody declared
|
|
2019
|
+
(`Not declared`, basis `undeclared`). A dimension is `verified` only when the scope's `verified` names it and it has
|
|
2020
|
+
a value; everything else a producer wrote is `declared`. With no scope every dimension is undeclared.
|
|
2021
|
+
|
|
2022
|
+
#### `scopeBadges`
|
|
2023
|
+
|
|
2024
|
+
```ts
|
|
2025
|
+
export function scopeBadges(scope: EvidenceScope | undefined): ScopeBadge[]
|
|
2026
|
+
```
|
|
2027
|
+
|
|
2028
|
+
The concise badges for a scope: one per declared dimension, in a fixed order (`Surface: Browser`, `Device: Simulator`, `Synthetic
|
|
2029
|
+
data`, `Scripted dependencies`, `Silent`, `Paced`). With no declared dimension the one badge `Scope unknown`, so
|
|
2030
|
+
a reviewer is told nothing was claimed rather than shown nothing.
|
|
2031
|
+
|
|
2032
|
+
#### `scopeCaveats`
|
|
2033
|
+
|
|
2034
|
+
```ts
|
|
2035
|
+
export function scopeCaveats(scope: EvidenceScope | undefined): string[]
|
|
2036
|
+
```
|
|
2037
|
+
|
|
2038
|
+
What a reviewer must not read into evidence: a sentence per thing that is not shown. It names the assertions the
|
|
2039
|
+
result stands on when there are some (so activity visible beyond them is not verified), and every kind of reality
|
|
2040
|
+
that was not declared. Empty only for a scope that declares and verifies everything and lists no limitation.
|
|
2041
|
+
|
|
1847
2042
|
### Report evidence types
|
|
1848
2043
|
|
|
1849
2044
|
Source: `packages/reports/src/evidence-types.ts`. Import: `@crvouga/atlas/reports/evidence-types`
|
|
@@ -1916,6 +2111,8 @@ export type VideoRef = {
|
|
|
1916
2111
|
readonly recording?: string;
|
|
1917
2112
|
/** False when the reference names no bytes at all (no bundle key, address or inline text), so it can never be played. */
|
|
1918
2113
|
readonly available: boolean;
|
|
2114
|
+
/** 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. */
|
|
2115
|
+
readonly scope?: EvidenceScope;
|
|
1919
2116
|
};
|
|
1920
2117
|
```
|
|
1921
2118
|
|
|
@@ -2419,7 +2616,7 @@ A number a check recorded. Exceeding `limit` is shown and never fails the check
|
|
|
2419
2616
|
#### `EvidenceExtras`
|
|
2420
2617
|
|
|
2421
2618
|
```ts
|
|
2422
|
-
export type EvidenceExtras = Partial<Pick<import('../../schema/src/layer').Check, 'tags' | 'labels' | 'links' | 'steps' | 'attachments' | 'annotations' | 'attempts' | 'extensions'>>;
|
|
2619
|
+
export type EvidenceExtras = Partial<Pick<import('../../schema/src/layer').Check, 'tags' | 'labels' | 'links' | 'steps' | 'attachments' | 'annotations' | 'attempts' | 'extensions' | 'scope'>>;
|
|
2423
2620
|
```
|
|
2424
2621
|
|
|
2425
2622
|
What a check may carry beyond its outcome. All optional and open: see docs/extending.md.
|
|
@@ -4333,7 +4530,7 @@ String choices (such as `{ device: 'iphone-15' }`) that make a result its own sl
|
|
|
4333
4530
|
#### `LayerExtras`
|
|
4334
4531
|
|
|
4335
4532
|
```ts
|
|
4336
|
-
export type LayerExtras = Partial<Pick<import('../../schema/src/layer').LayerDocument, 'tags' | 'labels' | 'links' | 'attachments' | 'annotations' | 'extensions' | 'recordings'>>;
|
|
4533
|
+
export type LayerExtras = Partial<Pick<import('../../schema/src/layer').LayerDocument, 'tags' | 'labels' | 'links' | 'attachments' | 'annotations' | 'extensions' | 'recordings' | 'scope'>>;
|
|
4337
4534
|
```
|
|
4338
4535
|
|
|
4339
4536
|
What a layer may carry as a whole. All optional and open: see docs/extending.md.
|
|
@@ -4473,7 +4670,8 @@ Routes, relative to where it is mounted:
|
|
|
4473
4670
|
runs of this machine. `cursor` is the previous page's; 409 when it no longer matches the listing (refresh).
|
|
4474
4671
|
Any `retry` value re-lists backends that were unavailable now instead of after the wait.
|
|
4475
4672
|
- `GET /evidence/rows/:backend/:id` the checks and spec fingerprints of one layer (`LayerRows`).
|
|
4476
|
-
- `GET /evidence/detail/:backend/:id?check=&kind=` the run-level evidence of an immutable layer,
|
|
4673
|
+
- `GET /evidence/detail/:backend/:id?check=&kind=` the run-level evidence of an immutable layer (and, as `parts`, what its
|
|
4674
|
+
parts carry as a whole), or one check's detail.
|
|
4477
4675
|
- `GET /layers/:id/blobs/*` an attachment of an immutable layer by its bundle key, served by `attachmentResponse`.
|
|
4478
4676
|
- `GET /layers/:id/*` the recorded `frame.css`, `tests/<test>/report.json` and `tests/<test>/files/<frame>` of a layer.
|
|
4479
4677
|
- `GET /live/:source/frame.css` and `GET /live/:source/tests/:testId/*` the same for a run still writing (uncached).
|
|
@@ -6636,7 +6834,7 @@ See docs/server.md#streaming-a-run-as-it-happens
|
|
|
6636
6834
|
#### `ReporterIdentity`
|
|
6637
6835
|
|
|
6638
6836
|
```ts
|
|
6639
|
-
export type ReporterIdentity = Pick<PublishChecksInput, 'producer' | 'kind' | 'title' | 'sha' | 'branch' | 'runUrl' | 'run' | 'environment' | 'parameters' | 'freshness' | 'specs' | 'tags' | 'labels' | 'links' | 'attachments' | 'annotations' | 'extensions'>;
|
|
6837
|
+
export type ReporterIdentity = Pick<PublishChecksInput, 'producer' | 'kind' | 'title' | 'sha' | 'branch' | 'runUrl' | 'run' | 'environment' | 'parameters' | 'freshness' | 'specs' | 'tags' | 'labels' | 'links' | 'attachments' | 'annotations' | 'scope' | 'extensions'>;
|
|
6640
6838
|
```
|
|
6641
6839
|
|
|
6642
6840
|
What every layer of one stream shares: who is reporting, from where. Whatever `publishChecks` takes, except the checks and the time.
|
|
@@ -8217,6 +8415,8 @@ export type TestSummary = {
|
|
|
8217
8415
|
readonly artifactTags?: readonly string[];
|
|
8218
8416
|
/** 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
8417
|
readonly inheritedArtifactTags?: readonly string[];
|
|
8418
|
+
/** 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. */
|
|
8419
|
+
readonly scopeProfiles?: readonly (readonly string[])[];
|
|
8220
8420
|
readonly labels: Readonly<Record<string, string>>;
|
|
8221
8421
|
/** What its current results carry: attachments, steps, retries, and the kinds of attachment. */
|
|
8222
8422
|
readonly evidence: { readonly attachments: number; readonly steps: number; readonly attempts: number; readonly roles: readonly string[] };
|
|
@@ -8309,6 +8509,8 @@ export type CatalogFacets = {
|
|
|
8309
8509
|
readonly tags: readonly FacetValue[];
|
|
8310
8510
|
/** The tags of attachments, normalized. Absent in documents of older servers. */
|
|
8311
8511
|
readonly artifactTags?: readonly FacetValue[];
|
|
8512
|
+
/** 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. */
|
|
8513
|
+
readonly scopes?: readonly FacetValue[];
|
|
8312
8514
|
readonly branches: readonly FacetValue[];
|
|
8313
8515
|
readonly labels: Readonly<Record<string, readonly FacetValue[]>>;
|
|
8314
8516
|
};
|
|
@@ -8350,6 +8552,8 @@ export type TestQuery = {
|
|
|
8350
8552
|
readonly artifactTag?: readonly string[];
|
|
8351
8553
|
/** 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
8554
|
readonly inherited?: boolean;
|
|
8555
|
+
/** 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. */
|
|
8556
|
+
readonly scope?: readonly string[];
|
|
8353
8557
|
/** Only tests recorded in this run (a key from `runList`). */
|
|
8354
8558
|
readonly run?: string;
|
|
8355
8559
|
readonly sort?: TestSort;
|
|
@@ -8429,14 +8633,14 @@ export type RunSummary = {
|
|
|
8429
8633
|
* and references that cannot be played. Absent when it has none, and in a report that carries no video information.
|
|
8430
8634
|
*/
|
|
8431
8635
|
readonly videos?: VideoCounts;
|
|
8636
|
+
/**
|
|
8637
|
+
* Files attached to the run itself, in its layers (and their parts), not to any one check: review movies, archives,
|
|
8638
|
+
* summaries. All kinds, videos included. Absent when none, and in a report that carries no such information.
|
|
8639
|
+
*/
|
|
8640
|
+
readonly attachments?: number;
|
|
8432
8641
|
/** The normalized tags of the attachments of its results and layers, sorted and distinct. Absent when none is tagged. */
|
|
8433
8642
|
readonly artifactTags?: readonly string[];
|
|
8434
8643
|
/** The tags of its checks and layers, which its attachments inherit (what `inherited=1` also matches), sorted and distinct. Absent when none. */
|
|
8435
|
-
readonly tags?: readonly string[];
|
|
8436
|
-
/** The media types (roles) of its attachments, sorted and distinct; `video` only for a playable recording. Absent when it has no attachment. */
|
|
8437
|
-
readonly types?: readonly string[];
|
|
8438
|
-
/** The `key=value` labels of its results and layers, sorted and distinct. Absent when none. */
|
|
8439
|
-
readonly labels?: readonly string[];
|
|
8440
8644
|
…
|
|
8441
8645
|
```
|
|
8442
8646
|
|
|
@@ -8494,6 +8698,29 @@ export type RunResult = {
|
|
|
8494
8698
|
|
|
8495
8699
|
One test's last result within one run.
|
|
8496
8700
|
|
|
8701
|
+
#### `RunLayer`
|
|
8702
|
+
|
|
8703
|
+
```ts
|
|
8704
|
+
export type RunLayer = {
|
|
8705
|
+
/** The layer's id: 64 hex digits for an archived layer. */
|
|
8706
|
+
readonly id: string;
|
|
8707
|
+
/** The backend it was read from, for `GET /evidence/detail/{backend}/{id}`. */
|
|
8708
|
+
readonly backend: string;
|
|
8709
|
+
readonly producer: string;
|
|
8710
|
+
readonly environment: string | null;
|
|
8711
|
+
readonly recordedAtMs: number;
|
|
8712
|
+
/** How many checks the layer recorded. A layer of zero checks is evidence only: it is not a passing test. */
|
|
8713
|
+
readonly checks: number;
|
|
8714
|
+
/** Files the layer carries as a whole (not those of its checks). */
|
|
8715
|
+
readonly attachments: number;
|
|
8716
|
+
/** Its videos, when it has any. */
|
|
8717
|
+
readonly videos?: VideoCounts;
|
|
8718
|
+
};
|
|
8719
|
+
```
|
|
8720
|
+
|
|
8721
|
+
One layer of a run, as the run's artifact hub names it: who recorded it, how many checks it holds (zero is possible
|
|
8722
|
+
and is not a pass) and the files it carries as a whole. Read the files with `GET /evidence/detail/{backend}/{id}`.
|
|
8723
|
+
|
|
8497
8724
|
#### `RunDetail`
|
|
8498
8725
|
|
|
8499
8726
|
```ts
|
|
@@ -8502,6 +8729,8 @@ export type RunDetail = {
|
|
|
8502
8729
|
readonly version: string;
|
|
8503
8730
|
readonly run: RunSummary;
|
|
8504
8731
|
readonly results: readonly RunResult[];
|
|
8732
|
+
/** Every layer of the run, oldest first, so its run-level files can be found even in a layer with no checks. Absent from an older server. */
|
|
8733
|
+
readonly layers?: readonly RunLayer[];
|
|
8505
8734
|
};
|
|
8506
8735
|
```
|
|
8507
8736
|
|
|
@@ -8607,7 +8836,7 @@ const catalog = testCatalog(catalogModel(layers, { history }));
|
|
|
8607
8836
|
- `of(key: string): readonly TestResultEntry[]` — The recorded results of one test (by `TestSummary.key`), newest first. Empty when none.
|
|
8608
8837
|
- `get runCount(): number { return this.#runs.size; }` — How many runs it knows of.
|
|
8609
8838
|
- `runs(): RunSummary[]` — Every run, newest first.
|
|
8610
|
-
- `run(key: string): { readonly run: RunSummary; readonly results: RunResult[] } | null` — One run and its results (failed first, then by spec and title), or null.
|
|
8839
|
+
- `run(key: string): { readonly run: RunSummary; readonly results: RunResult[]; readonly layers: RunLayer[] } | null` — One run and its results (failed first, then by spec and title), or null.
|
|
8611
8840
|
- `evidenceResults(): readonly { readonly result: ArtifactResult; readonly fields: FilterFields }[]` — Every recorded result that carries an attachment, newest first, with the values a query looks at. Built once per layer added. The domain is left to the caller (`artifactList`), which may know more specs than the layers recorded.
|
|
8612
8841
|
- `testsIn(key: string): ReadonlySet<string>` — The keys of the tests one run recorded.
|
|
8613
8842
|
|
|
@@ -8762,7 +8991,7 @@ One run with every result it recorded, or null when no run has that key.
|
|
|
8762
8991
|
export function parseTestQuery(params: URLSearchParams): TestQuery
|
|
8763
8992
|
```
|
|
8764
8993
|
|
|
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`.
|
|
8994
|
+
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
8995
|
|
|
8767
8996
|
#### `formatMs`
|
|
8768
8997
|
|
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
|