@crvouga/atlas 1.1.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 +12 -0
- package/dist/reports/src/artifact-query.d.ts +123 -0
- package/dist/reports/src/artifact-query.js +234 -0
- package/dist/reports/src/evidence-detail.d.ts +14 -3
- package/dist/reports/src/evidence-detail.js +34 -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 +5 -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 +108 -6
- package/dist/reports/src/test-catalog.js +155 -8
- package/dist/schema/src/api.d.ts +745 -0
- package/dist/schema/src/api.js +48 -6
- package/dist/schema/src/layer.d.ts +981 -0
- package/dist/schema/src/layer.js +43 -4
- package/dist/server/src/app.js +16 -5
- package/dist/server/src/openapi.js +42 -3
- package/docs/extending.md +1 -0
- package/docs/reference/http-api.md +86 -2
- package/docs/reference/reports.md +550 -14
- package/docs/reference/schema.md +55 -0
- package/docs/rich-evidence.md +78 -0
- package/docs/server.md +4 -1
- package/docs/ui.md +1 -1
- package/llms-full.txt +170 -4
- package/package.json +1 -1
- package/packages/schema/schemas/check-detail.schema.json +673 -1
- package/packages/schema/schemas/effective-delta.schema.json +206 -0
- package/packages/schema/schemas/effective-evidence.schema.json +206 -0
- package/packages/schema/schemas/evidence-page.schema.json +103 -0
- package/packages/schema/schemas/layer-rows.schema.json +103 -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 +131 -0
- package/packages/schema/schemas/report-runs.schema.json +221 -0
- package/packages/schema/schemas/test-catalog.schema.json +135 -0
- package/packages/schema/schemas/test-history.schema.json +216 -0
- package/packages/server/openapi.json +4731 -735
- 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/ArtifactsView.tsx +162 -0
- package/packages/visualizer/src/explorer/Explorer.tsx +47 -3
- package/packages/visualizer/src/explorer/RunsView.tsx +43 -19
- package/packages/visualizer/src/explorer/catalog.tsx +97 -12
- package/packages/visualizer/src/explorer/filters.tsx +279 -0
- package/packages/visualizer/src/explorer/search.ts +85 -6
- package/packages/visualizer/src/explorer/views.ts +75 -0
|
@@ -4,6 +4,257 @@ Layered evidence for any kind of spec: publishing layers, stores (folder, S3, an
|
|
|
4
4
|
|
|
5
5
|
> Generated from the doc comments of `packages/reports/src` by `bun run docs`. Do not edit: change the comment next to the code.
|
|
6
6
|
|
|
7
|
+
### Run and artifact queries
|
|
8
|
+
|
|
9
|
+
Source: `packages/reports/src/artifact-query.ts`. Import: `@crvouga/atlas/reports/artifact-query`
|
|
10
|
+
|
|
11
|
+
One shareable query for the runs of a project and for the recorded results that carry evidence (recordings,
|
|
12
|
+
screenshots, traces): by artifact tag, label, media type, feature (domain, spec), environment or device, producer,
|
|
13
|
+
branch, revision, outcome and time. The same rules answer `GET /api/reports/runs`, `GET /api/reports/artifacts`
|
|
14
|
+
and the viewer's Runs and Artifacts views, so a link means the same thing everywhere.
|
|
15
|
+
|
|
16
|
+
The semantics are fixed, not inferred:
|
|
17
|
+
|
|
18
|
+
- Across dimensions the filters combine with AND: `artifactTag=demo&type=video&environment=ios` is a demo
|
|
19
|
+
recording on ios.
|
|
20
|
+
- Within a dimension the values combine with OR: `environment=ios,android` is either.
|
|
21
|
+
- When both `artifactTag` and `type` are asked for, one artifact must be both (a `demo`-tagged video, not a demo-tagged
|
|
22
|
+
screenshot beside an untagged video) wherever the report says which videos carry which tags; otherwise a result with
|
|
23
|
+
an artifact of that type and an artifact with that tag matches.
|
|
24
|
+
- Facet counts are disjunctive: the count beside a value is how many records the list would hold if that value were
|
|
25
|
+
the only choice in its dimension, with every other dimension's filter applied. With a value already chosen, the
|
|
26
|
+
other values show what choosing them in addition would add, and the chosen one's count is the list's.
|
|
27
|
+
- A filter never changes a status. Evidence that is not recorded is unknown, never a match.
|
|
28
|
+
|
|
29
|
+
Lists in the address are comma-separated; a comma or backslash inside a value is written `\,` or `\\`
|
|
30
|
+
({@link splitList}, {@link joinList}), so a label such as `note=a,b` survives.
|
|
31
|
+
|
|
32
|
+
See docs/server.md#reading-the-report-as-an-agent
|
|
33
|
+
|
|
34
|
+
#### `QueryFacetValue`
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
export type QueryFacetValue = { readonly value: string; readonly count: number };
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
A value of a dimension and how many records have it.
|
|
41
|
+
|
|
42
|
+
#### `QUERY_DIMENSIONS`
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
export const QUERY_DIMENSIONS = …
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The dimensions a query narrows by, with their address parameters.
|
|
49
|
+
|
|
50
|
+
#### `QueryDimension`
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
export type QueryDimension = (typeof QUERY_DIMENSIONS)[number];
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
One of {@link QUERY_DIMENSIONS}.
|
|
57
|
+
|
|
58
|
+
#### `ArtifactFilter`
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
export type ArtifactFilter = {
|
|
62
|
+
/** Words (all of them) found in the title, test id, spec, domain, branch, labels, tags, environment or producer; or, for a run, its title. */
|
|
63
|
+
readonly q?: string;
|
|
64
|
+
/** Artifact tags (`demo` and `#demo` are the same). */
|
|
65
|
+
readonly artifactTag?: readonly string[];
|
|
66
|
+
/** Also match the tags an artifact inherits from its check and run. Default false: the exact artifact query. */
|
|
67
|
+
readonly inherited?: boolean;
|
|
68
|
+
/** Media types as roles: `video`, `image`, `audio`, `snapshot`, `text`, `file`, or a producer's own role. `video` means a playable recording. */
|
|
69
|
+
readonly type?: readonly string[];
|
|
70
|
+
/** `key=value`, or `key` for any value. */
|
|
71
|
+
readonly label?: readonly string[];
|
|
72
|
+
/** Environments or devices (`ios`, `desktop-web`). */
|
|
73
|
+
readonly environment?: readonly string[];
|
|
74
|
+
readonly producer?: readonly string[];
|
|
75
|
+
readonly branch?: readonly string[];
|
|
76
|
+
/** Revisions: a full commit id, or a prefix of at least seven characters. */
|
|
77
|
+
readonly sha?: readonly string[];
|
|
78
|
+
/** Statuses (`passed`, `failed`, `running`, `not-run`). A run's outcome is `failed` when any result failed, else `running`, else `passed`, else `not-run`. */
|
|
79
|
+
readonly outcome?: readonly string[];
|
|
80
|
+
/** The feature domain of a result's spec. Not applicable to runs. */
|
|
81
|
+
readonly domain?: readonly string[];
|
|
82
|
+
/** Spec paths. Not applicable to runs. */
|
|
83
|
+
readonly spec?: readonly string[];
|
|
84
|
+
/** Only records at or after this time (epoch ms): a result's recording time, a run's end. */
|
|
85
|
+
readonly since?: number;
|
|
86
|
+
/** Only records at or before this time (epoch ms). */
|
|
87
|
+
readonly until?: number;
|
|
88
|
+
/** Only records with a playable video recording (the same as `type: ['video']`). */
|
|
89
|
+
readonly video?: boolean;
|
|
90
|
+
};
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
What a query asks for. Every field is optional; nothing asked is everything. See the module comment for the semantics.
|
|
94
|
+
|
|
95
|
+
#### `FilterName`
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
export type FilterName = QueryDimension | 'q' | 'inherited' | 'since' | 'until' | 'video';
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The filters a query used, as they are named in the address and in `filters` of a response.
|
|
102
|
+
|
|
103
|
+
#### `FilterFields`
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
export type FilterFields = {
|
|
107
|
+
/** Lower-cased text a search matches. */
|
|
108
|
+
readonly text: string;
|
|
109
|
+
/** The normalized tags of its artifacts. */
|
|
110
|
+
readonly artifactTags: readonly string[];
|
|
111
|
+
/** The tags its artifacts inherit from checks and runs, and its own. */
|
|
112
|
+
readonly tags: readonly string[];
|
|
113
|
+
/** Distinct media types; `video` only when a recording can be played. */
|
|
114
|
+
readonly types: readonly string[];
|
|
115
|
+
/** Artifacts by type and their own tags. Where the report does not say which artifact carries which tag, every artifact of the record is given the record's artifact tags. */
|
|
116
|
+
readonly media: readonly { readonly type: string; readonly tags: readonly string[] }[];
|
|
117
|
+
/** `key=value`, sorted and distinct. */
|
|
118
|
+
readonly labels: readonly string[];
|
|
119
|
+
readonly environments: readonly string[];
|
|
120
|
+
readonly producers: readonly string[];
|
|
121
|
+
readonly branches: readonly string[];
|
|
122
|
+
readonly shas: readonly string[];
|
|
123
|
+
readonly outcomes: readonly string[];
|
|
124
|
+
/** Absent when the dimension does not apply (runs have no domain). */
|
|
125
|
+
readonly domains?: readonly string[];
|
|
126
|
+
readonly specs?: readonly string[];
|
|
127
|
+
readonly at: number;
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The values of a record a query looks at: made by {@link fieldsOfRun} or by the artifact search.
|
|
132
|
+
|
|
133
|
+
#### `QueryFacets`
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
export type QueryFacets = {
|
|
137
|
+
readonly artifactTags: readonly QueryFacetValue[];
|
|
138
|
+
readonly types: readonly QueryFacetValue[];
|
|
139
|
+
/** Values are `key=value`. */
|
|
140
|
+
readonly labels: readonly QueryFacetValue[];
|
|
141
|
+
readonly environments: readonly QueryFacetValue[];
|
|
142
|
+
readonly producers: readonly QueryFacetValue[];
|
|
143
|
+
readonly branches: readonly QueryFacetValue[];
|
|
144
|
+
readonly outcomes: readonly QueryFacetValue[];
|
|
145
|
+
readonly domains?: readonly QueryFacetValue[];
|
|
146
|
+
};
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The facets of a query: for each dimension, its values with disjunctive counts (see the module comment).
|
|
150
|
+
|
|
151
|
+
#### `splitList`
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
export function splitList(raw: string): string[]
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Splits a comma-separated list from an address: `a,b` is two values; `\,` is a comma and `\\` a backslash inside a
|
|
158
|
+
value. Values are trimmed and empty ones dropped.
|
|
159
|
+
|
|
160
|
+
**Example**
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
splitList('demo,release\\,2'); // ['demo', 'release,2']
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### `joinList`
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
export function joinList(list: readonly string[]): string | undefined
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Joins values into one address parameter that {@link splitList} reads back; `undefined` when there are none.
|
|
173
|
+
|
|
174
|
+
#### `parseTime`
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
export function parseTime(raw: string | null | undefined): number | undefined
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
A time from an address: epoch milliseconds, or a date or date-time Date can read (a date alone is UTC). `undefined` when unreadable.
|
|
181
|
+
|
|
182
|
+
#### `parseArtifactFilter`
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
export function parseArtifactFilter(params: URLSearchParams): ArtifactFilter
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Reads a query from URL search parameters: `q`, `video`, `inherited`, `since`, `until` and the repeatable or
|
|
189
|
+
comma-separated {@link QUERY_DIMENSIONS}. Unreadable values are left out, never an error.
|
|
190
|
+
|
|
191
|
+
#### `filterParams`
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
export function filterParams(filter: ArtifactFilter): URLSearchParams
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
A query as URL search parameters, the form {@link parseArtifactFilter} reads back.
|
|
198
|
+
|
|
199
|
+
#### `usedFilters`
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
export function usedFilters(filter: ArtifactFilter): FilterName[]
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Which filters a query uses, by the names an address gives them.
|
|
206
|
+
|
|
207
|
+
#### `isFiltered`
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
export function isFiltered(filter: ArtifactFilter): boolean
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Whether a query narrows anything.
|
|
214
|
+
|
|
215
|
+
#### `forRuns`
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
export function forRuns(filter: ArtifactFilter): ArtifactFilter
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Every filter in `filter` that may be applied to a record with these fields, as the runs query applies them (no domain or spec).
|
|
222
|
+
|
|
223
|
+
#### `matchesFilter`
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
export function matchesFilter(fields: FilterFields, filter: ArtifactFilter, skip?: QueryDimension): boolean
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Whether a record satisfies a query. `skip` leaves one dimension out: what {@link facetsOf} uses to count the values of
|
|
230
|
+
that dimension, and nothing else should need.
|
|
231
|
+
|
|
232
|
+
#### `facetsOf`
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
export function facetsOf(records: readonly FilterFields[], filter: ArtifactFilter): QueryFacets
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The facets of a query over every record: for each dimension, the values the records have and how many records
|
|
239
|
+
would match if that value were chosen in its dimension (see the module comment). A value chosen in the query is
|
|
240
|
+
listed even when nothing has it, with a count of 0, so it can be removed.
|
|
241
|
+
|
|
242
|
+
#### `outcomeOfCounts`
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
export function outcomeOfCounts(counts: Readonly<Record<string, number>>): string
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The outcome of a run from its results by status: `failed`, else `running`, else `passed`, else `not-run`.
|
|
249
|
+
|
|
250
|
+
#### `labelList`
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
export function labelList(labels: Readonly<Record<string, string>> | undefined): string[]
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The `key=value` labels of a record, sorted and distinct.
|
|
257
|
+
|
|
7
258
|
### Reviewer tags in a store
|
|
8
259
|
|
|
9
260
|
Source: `packages/reports/src/artifact-tag-store.ts`. Import: `@crvouga/atlas/reports/artifact-tag-store`
|
|
@@ -1410,6 +1661,8 @@ export type EvidenceHolder = {
|
|
|
1410
1661
|
readonly steps?: readonly Step[];
|
|
1411
1662
|
readonly attempts?: readonly Attempt[];
|
|
1412
1663
|
readonly extensions?: Readonly<Record<string, unknown>>;
|
|
1664
|
+
/** What its evidence shows, as its producer declared it (see `EvidenceScope`). */
|
|
1665
|
+
readonly scope?: EvidenceScope;
|
|
1413
1666
|
};
|
|
1414
1667
|
```
|
|
1415
1668
|
|
|
@@ -1429,7 +1682,17 @@ Several holders (for example a check and its layer) are summed: `attachments` al
|
|
|
1429
1682
|
attempts, `steps` counts only each holder's own top-level steps, tags are de-duplicated, and for a repeated
|
|
1430
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
|
|
1431
1684
|
name implies), sorted and distinct. `videos` lists every attachment whose effective media type is video, with the
|
|
1432
|
-
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`.
|
|
1433
1696
|
|
|
1434
1697
|
#### `summarizeLayerEvidence`
|
|
1435
1698
|
|
|
@@ -1479,6 +1742,8 @@ export type CheckDetail = {
|
|
|
1479
1742
|
readonly check: Check;
|
|
1480
1743
|
/** What the run as a whole carries: its logs, its links, its labels. */
|
|
1481
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;
|
|
1482
1747
|
};
|
|
1483
1748
|
```
|
|
1484
1749
|
|
|
@@ -1487,7 +1752,7 @@ All of one result's evidence, read when someone opens it.
|
|
|
1487
1752
|
#### `checkDetailOf`
|
|
1488
1753
|
|
|
1489
1754
|
```ts
|
|
1490
|
-
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
|
|
1491
1756
|
```
|
|
1492
1757
|
|
|
1493
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.
|
|
@@ -1498,7 +1763,7 @@ One result's evidence, from a layer: the first check with id `checkId` (in a par
|
|
|
1498
1763
|
export function runEvidence(layer: EvidenceHolder): EvidenceHolder
|
|
1499
1764
|
```
|
|
1500
1765
|
|
|
1501
|
-
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.
|
|
1502
1767
|
|
|
1503
1768
|
#### `bundleKeysOf`
|
|
1504
1769
|
|
|
@@ -1593,6 +1858,156 @@ the next pass; `refresh` does not throw for those.
|
|
|
1593
1858
|
|
|
1594
1859
|
- `args.backend`: The backend id stamped on live layers.
|
|
1595
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
|
+
|
|
1596
2011
|
### Report evidence types
|
|
1597
2012
|
|
|
1598
2013
|
Source: `packages/reports/src/evidence-types.ts`. Import: `@crvouga/atlas/reports/evidence-types`
|
|
@@ -1659,10 +2074,14 @@ export type VideoRef = {
|
|
|
1659
2074
|
readonly attempt?: number;
|
|
1660
2075
|
/** The name of the step that holds it, when `owner` is `step`. */
|
|
1661
2076
|
readonly step?: string;
|
|
2077
|
+
/** The normalized tags of this attachment itself (`demo`), so a query can ask for a tagged video rather than a result that has a video and some tagged file. Absent when it has none (and in documents of older servers). */
|
|
2078
|
+
readonly tags?: readonly string[];
|
|
1662
2079
|
/** What identifies the bytes (digest, bundle key or address); references with the same one are the same recording. Absent when `available` is false. */
|
|
1663
2080
|
readonly recording?: string;
|
|
1664
2081
|
/** False when the reference names no bytes at all (no bundle key, address or inline text), so it can never be played. */
|
|
1665
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;
|
|
1666
2085
|
};
|
|
1667
2086
|
```
|
|
1668
2087
|
|
|
@@ -2166,7 +2585,7 @@ A number a check recorded. Exceeding `limit` is shown and never fails the check
|
|
|
2166
2585
|
#### `EvidenceExtras`
|
|
2167
2586
|
|
|
2168
2587
|
```ts
|
|
2169
|
-
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'>>;
|
|
2170
2589
|
```
|
|
2171
2590
|
|
|
2172
2591
|
What a check may carry beyond its outcome. All optional and open: see docs/extending.md.
|
|
@@ -4080,7 +4499,7 @@ String choices (such as `{ device: 'iphone-15' }`) that make a result its own sl
|
|
|
4080
4499
|
#### `LayerExtras`
|
|
4081
4500
|
|
|
4082
4501
|
```ts
|
|
4083
|
-
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'>>;
|
|
4084
4503
|
```
|
|
4085
4504
|
|
|
4086
4505
|
What a layer may carry as a whole. All optional and open: see docs/extending.md.
|
|
@@ -6383,7 +6802,7 @@ See docs/server.md#streaming-a-run-as-it-happens
|
|
|
6383
6802
|
#### `ReporterIdentity`
|
|
6384
6803
|
|
|
6385
6804
|
```ts
|
|
6386
|
-
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'>;
|
|
6387
6806
|
```
|
|
6388
6807
|
|
|
6389
6808
|
What every layer of one stream shares: who is reporting, from where. Whatever `publishChecks` takes, except the checks and the time.
|
|
@@ -7811,7 +8230,9 @@ runs that produced them and which tests each one ran.
|
|
|
7811
8230
|
always the report's winner (`combineEvidence`), and history only describes how it got there.
|
|
7812
8231
|
- `testCatalog` turns the report's winners (plus a history, when there is one) into a `TestCatalog`: a summary with
|
|
7813
8232
|
insights (slowest, flaky, failing), facets to filter by, and the tests that match a `TestQuery`.
|
|
7814
|
-
- `testHistory`, `runList` and `runDetail` answer for one test, every run, and one run.
|
|
8233
|
+
- `testHistory`, `runList` and `runDetail` answer for one test, every run, and one run. `runList` and `artifactList`
|
|
8234
|
+
narrow by tag, label, media type, environment, branch, outcome and time (see `artifact-query.ts`); `artifactList` finds
|
|
8235
|
+
the recorded results that carry evidence across the whole history, not only the latest winners.
|
|
7815
8236
|
- `catalogMarkdown`, `historyMarkdown`, `runsMarkdown` and `runMarkdown` say the same in Markdown, for an agent or a
|
|
7816
8237
|
terminal.
|
|
7817
8238
|
|
|
@@ -7962,6 +8383,8 @@ export type TestSummary = {
|
|
|
7962
8383
|
readonly artifactTags?: readonly string[];
|
|
7963
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. */
|
|
7964
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[])[];
|
|
7965
8388
|
readonly labels: Readonly<Record<string, string>>;
|
|
7966
8389
|
/** What its current results carry: attachments, steps, retries, and the kinds of attachment. */
|
|
7967
8390
|
readonly evidence: { readonly attachments: number; readonly steps: number; readonly attempts: number; readonly roles: readonly string[] };
|
|
@@ -8054,6 +8477,8 @@ export type CatalogFacets = {
|
|
|
8054
8477
|
readonly tags: readonly FacetValue[];
|
|
8055
8478
|
/** The tags of attachments, normalized. Absent in documents of older servers. */
|
|
8056
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[];
|
|
8057
8482
|
readonly branches: readonly FacetValue[];
|
|
8058
8483
|
readonly labels: Readonly<Record<string, readonly FacetValue[]>>;
|
|
8059
8484
|
};
|
|
@@ -8095,6 +8520,8 @@ export type TestQuery = {
|
|
|
8095
8520
|
readonly artifactTag?: readonly string[];
|
|
8096
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. */
|
|
8097
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[];
|
|
8098
8525
|
/** Only tests recorded in this run (a key from `runList`). */
|
|
8099
8526
|
readonly run?: string;
|
|
8100
8527
|
readonly sort?: TestSort;
|
|
@@ -8174,7 +8601,15 @@ export type RunSummary = {
|
|
|
8174
8601
|
* and references that cannot be played. Absent when it has none, and in a report that carries no video information.
|
|
8175
8602
|
*/
|
|
8176
8603
|
readonly videos?: VideoCounts;
|
|
8177
|
-
|
|
8604
|
+
/** The normalized tags of the attachments of its results and layers, sorted and distinct. Absent when none is tagged. */
|
|
8605
|
+
readonly artifactTags?: readonly string[];
|
|
8606
|
+
/** The tags of its checks and layers, which its attachments inherit (what `inherited=1` also matches), sorted and distinct. Absent when none. */
|
|
8607
|
+
readonly tags?: readonly string[];
|
|
8608
|
+
/** The media types (roles) of its attachments, sorted and distinct; `video` only for a playable recording. Absent when it has no attachment. */
|
|
8609
|
+
readonly types?: readonly string[];
|
|
8610
|
+
/** The `key=value` labels of its results and layers, sorted and distinct. Absent when none. */
|
|
8611
|
+
readonly labels?: readonly string[];
|
|
8612
|
+
…
|
|
8178
8613
|
```
|
|
8179
8614
|
|
|
8180
8615
|
A run: the layers that name the same execution (`run.id`), else share a CI run URL, else a branch and commit. When,
|
|
@@ -8183,13 +8618,11 @@ where, and how its tests went.
|
|
|
8183
8618
|
#### `RunQuery`
|
|
8184
8619
|
|
|
8185
8620
|
```ts
|
|
8186
|
-
export type RunQuery =
|
|
8187
|
-
/** Only runs that have at least one playable video recording. `total` then counts only those. */
|
|
8188
|
-
readonly video?: boolean;
|
|
8189
|
-
};
|
|
8621
|
+
export type RunQuery = ArtifactFilter;
|
|
8190
8622
|
```
|
|
8191
8623
|
|
|
8192
|
-
What `runList` can be asked
|
|
8624
|
+
What `runList` can be asked: an {@link ArtifactFilter}, of which a run answers every dimension but `domain` and
|
|
8625
|
+
`spec`. `video` keeps only runs that have at least one playable video recording; `total` then counts only those.
|
|
8193
8626
|
|
|
8194
8627
|
#### `RunList`
|
|
8195
8628
|
|
|
@@ -8202,6 +8635,10 @@ export type RunList = {
|
|
|
8202
8635
|
readonly runs: readonly RunSummary[];
|
|
8203
8636
|
/** How many of all runs (whatever was asked, before `limit`) have a playable video recording. */
|
|
8204
8637
|
readonly withVideo?: number;
|
|
8638
|
+
/** The filters of the query that were applied. A viewer that asked for another knows the server ignored it. */
|
|
8639
|
+
readonly filters?: readonly FilterName[];
|
|
8640
|
+
/** The values each filter dimension offers with counts over every run (see `facetsOf`). */
|
|
8641
|
+
readonly facets?: QueryFacets;
|
|
8205
8642
|
};
|
|
8206
8643
|
```
|
|
8207
8644
|
|
|
@@ -8242,6 +8679,70 @@ export type RunDetail = {
|
|
|
8242
8679
|
|
|
8243
8680
|
`GET /api/reports/runs/detail`: one run and every result it recorded.
|
|
8244
8681
|
|
|
8682
|
+
#### `ArtifactResult`
|
|
8683
|
+
|
|
8684
|
+
```ts
|
|
8685
|
+
export type ArtifactResult = {
|
|
8686
|
+
/** The test's key, as in `TestSummary.key`. */
|
|
8687
|
+
readonly key: string;
|
|
8688
|
+
readonly id: string;
|
|
8689
|
+
readonly title: string;
|
|
8690
|
+
readonly specPath: string | null;
|
|
8691
|
+
/** The domain of its spec, or null when it has none. */
|
|
8692
|
+
readonly domain: string | null;
|
|
8693
|
+
/** When it was recorded, epoch ms. */
|
|
8694
|
+
readonly at: number;
|
|
8695
|
+
readonly status: EvidenceStatus;
|
|
8696
|
+
readonly environment: string | null;
|
|
8697
|
+
readonly producer: string;
|
|
8698
|
+
/** The kind of the part that held it. */
|
|
8699
|
+
readonly kind: string;
|
|
8700
|
+
readonly parameters: Readonly<Record<string, string>>;
|
|
8701
|
+
readonly branch: string | null;
|
|
8702
|
+
readonly sha: string | null;
|
|
8703
|
+
/** The key of its run (see `runKey`). */
|
|
8704
|
+
readonly run: string;
|
|
8705
|
+
readonly runUrl: string | null;
|
|
8706
|
+
/** The id of the layer that recorded it: with `key`, the exact result to open. */
|
|
8707
|
+
readonly layer: string;
|
|
8708
|
+
readonly backend: string;
|
|
8709
|
+
/** Attachments on the result, including those on its steps and attempts. */
|
|
8710
|
+
readonly attachments: number;
|
|
8711
|
+
/** The normalized tags of its attachments. */
|
|
8712
|
+
readonly artifactTags: readonly string[];
|
|
8713
|
+
/** The tags of its check and run, which its attachments inherit. */
|
|
8714
|
+
readonly tags: readonly string[];
|
|
8715
|
+
/** The media types (roles) of its attachments; `video` only for a playable recording. */
|
|
8716
|
+
readonly types: readonly string[];
|
|
8717
|
+
readonly labels: Readonly<Record<string, string>>;
|
|
8718
|
+
readonly videos?: readonly VideoRef[];
|
|
8719
|
+
};
|
|
8720
|
+
```
|
|
8721
|
+
|
|
8722
|
+
One recorded result that carries evidence (an attachment: a recording, a screenshot, a trace), with where and when it
|
|
8723
|
+
was recorded: the unit `artifactList` finds. It is a result in the history, not only the report's current winner, so
|
|
8724
|
+
a recording tagged `demo` is found with its original run, revision and status even when a later attempt is untagged.
|
|
8725
|
+
|
|
8726
|
+
#### `ArtifactList`
|
|
8727
|
+
|
|
8728
|
+
```ts
|
|
8729
|
+
export type ArtifactList = {
|
|
8730
|
+
readonly schemaVersion: typeof TEST_CATALOG_VERSION;
|
|
8731
|
+
readonly version: string;
|
|
8732
|
+
/** Results that matched, before `offset` and `limit`. */
|
|
8733
|
+
readonly total: number;
|
|
8734
|
+
readonly offset: number;
|
|
8735
|
+
readonly limit: number;
|
|
8736
|
+
readonly results: readonly ArtifactResult[];
|
|
8737
|
+
/** The filters of the query that were applied. */
|
|
8738
|
+
readonly filters: readonly FilterName[];
|
|
8739
|
+
/** The values each dimension offers with disjunctive counts over every result that carries evidence. */
|
|
8740
|
+
readonly facets: QueryFacets;
|
|
8741
|
+
};
|
|
8742
|
+
```
|
|
8743
|
+
|
|
8744
|
+
`GET /api/reports/artifacts`: the recorded results that carry evidence and match a query, newest first.
|
|
8745
|
+
|
|
8245
8746
|
#### `runKey`
|
|
8246
8747
|
|
|
8247
8748
|
```ts
|
|
@@ -8279,6 +8780,7 @@ const catalog = testCatalog(catalogModel(layers, { history }));
|
|
|
8279
8780
|
- `get runCount(): number { return this.#runs.size; }` — How many runs it knows of.
|
|
8280
8781
|
- `runs(): RunSummary[]` — Every run, newest first.
|
|
8281
8782
|
- `run(key: string): { readonly run: RunSummary; readonly results: RunResult[] } | null` — One run and its results (failed first, then by spec and title), or null.
|
|
8783
|
+
- `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.
|
|
8282
8784
|
- `testsIn(key: string): ReadonlySet<string>` — The keys of the tests one run recorded.
|
|
8283
8785
|
|
|
8284
8786
|
#### `durationStats`
|
|
@@ -8392,6 +8894,32 @@ Every run the history knows of, newest first, `limit` at most (default 100). Wit
|
|
|
8392
8894
|
have a playable video recording: the filter and `total` apply to every run before `limit`, and nothing is read from
|
|
8393
8895
|
any recording's bytes. `withVideo` always counts every run that has one.
|
|
8394
8896
|
|
|
8897
|
+
#### `fieldsOfRun`
|
|
8898
|
+
|
|
8899
|
+
```ts
|
|
8900
|
+
export function fieldsOfRun(run: RunSummary): FilterFields
|
|
8901
|
+
```
|
|
8902
|
+
|
|
8903
|
+
What a query looks at in a run: see {@link ArtifactFilter}. A run's outcome is `failed` when any result failed.
|
|
8904
|
+
|
|
8905
|
+
#### `artifactList`
|
|
8906
|
+
|
|
8907
|
+
```ts
|
|
8908
|
+
export function artifactList(history: TestHistory, version: string, query: ArtifactFilter = {}, page: { readonly limit?: number; readonly offset?: number; readonly domainOf?: (specPath: string) => string | undefined } = {}): ArtifactList
|
|
8909
|
+
```
|
|
8910
|
+
|
|
8911
|
+
The recorded results that carry evidence and match a query, newest first, across the whole history (`limit` default
|
|
8912
|
+
100). `total` and the facet counts apply to every match before `offset` and `limit`. `domainOf` tells the domain of a
|
|
8913
|
+
spec path when the caller knows more than the layers recorded (a server with a spec set); otherwise it is the
|
|
8914
|
+
recorded spec's domain, else the folder the path implies.
|
|
8915
|
+
|
|
8916
|
+
**Example**
|
|
8917
|
+
|
|
8918
|
+
```ts
|
|
8919
|
+
const list = artifactList(history, 'v1', { artifactTag: ['demo'], type: ['video'], environment: ['ios'] });
|
|
8920
|
+
list.results.map(r => [r.title, r.run, r.sha, r.status]);
|
|
8921
|
+
```
|
|
8922
|
+
|
|
8395
8923
|
#### `runDetail`
|
|
8396
8924
|
|
|
8397
8925
|
```ts
|
|
@@ -8406,7 +8934,7 @@ One run with every result it recorded, or null when no run has that key.
|
|
|
8406
8934
|
export function parseTestQuery(params: URLSearchParams): TestQuery
|
|
8407
8935
|
```
|
|
8408
8936
|
|
|
8409
|
-
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`.
|
|
8410
8938
|
|
|
8411
8939
|
#### `formatMs`
|
|
8412
8940
|
|
|
@@ -8440,6 +8968,14 @@ export function runsMarkdown(list: RunList): string
|
|
|
8440
8968
|
|
|
8441
8969
|
The run list as Markdown.
|
|
8442
8970
|
|
|
8971
|
+
#### `artifactsMarkdown`
|
|
8972
|
+
|
|
8973
|
+
```ts
|
|
8974
|
+
export function artifactsMarkdown(list: ArtifactList): string
|
|
8975
|
+
```
|
|
8976
|
+
|
|
8977
|
+
The artifact results as Markdown, for an agent or a terminal.
|
|
8978
|
+
|
|
8443
8979
|
#### `runMarkdown`
|
|
8444
8980
|
|
|
8445
8981
|
```ts
|