@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/reports/src/artifact-query.d.ts +123 -0
  3. package/dist/reports/src/artifact-query.js +234 -0
  4. package/dist/reports/src/evidence-detail.d.ts +14 -3
  5. package/dist/reports/src/evidence-detail.js +34 -19
  6. package/dist/reports/src/evidence-scope.d.ts +83 -0
  7. package/dist/reports/src/evidence-scope.js +129 -0
  8. package/dist/reports/src/evidence-types.d.ts +5 -1
  9. package/dist/reports/src/explorer-types.d.ts +1 -1
  10. package/dist/reports/src/layer-merge.js +3 -2
  11. package/dist/reports/src/layer-types.d.ts +1 -1
  12. package/dist/reports/src/publish-checks.js +1 -0
  13. package/dist/reports/src/reporter.d.ts +1 -1
  14. package/dist/reports/src/reports-explorer-route.d.ts +276 -0
  15. package/dist/reports/src/reports-route.d.ts +276 -0
  16. package/dist/reports/src/test-catalog.d.ts +108 -6
  17. package/dist/reports/src/test-catalog.js +155 -8
  18. package/dist/schema/src/api.d.ts +745 -0
  19. package/dist/schema/src/api.js +48 -6
  20. package/dist/schema/src/layer.d.ts +981 -0
  21. package/dist/schema/src/layer.js +43 -4
  22. package/dist/server/src/app.js +16 -5
  23. package/dist/server/src/openapi.js +42 -3
  24. package/docs/extending.md +1 -0
  25. package/docs/reference/http-api.md +86 -2
  26. package/docs/reference/reports.md +550 -14
  27. package/docs/reference/schema.md +55 -0
  28. package/docs/rich-evidence.md +78 -0
  29. package/docs/server.md +4 -1
  30. package/docs/ui.md +1 -1
  31. package/llms-full.txt +170 -4
  32. package/package.json +1 -1
  33. package/packages/schema/schemas/check-detail.schema.json +673 -1
  34. package/packages/schema/schemas/effective-delta.schema.json +206 -0
  35. package/packages/schema/schemas/effective-evidence.schema.json +206 -0
  36. package/packages/schema/schemas/evidence-page.schema.json +103 -0
  37. package/packages/schema/schemas/layer-rows.schema.json +103 -0
  38. package/packages/schema/schemas/layer.schema.json +1598 -62
  39. package/packages/schema/schemas/publish-checks.schema.json +768 -0
  40. package/packages/schema/schemas/report-run.schema.json +131 -0
  41. package/packages/schema/schemas/report-runs.schema.json +221 -0
  42. package/packages/schema/schemas/test-catalog.schema.json +135 -0
  43. package/packages/schema/schemas/test-history.schema.json +216 -0
  44. package/packages/server/openapi.json +4731 -735
  45. package/packages/visualizer/src/evidence/artifacts.tsx +24 -18
  46. package/packages/visualizer/src/evidence/evidence-scope.tsx +106 -0
  47. package/packages/visualizer/src/evidence/format.ts +11 -0
  48. package/packages/visualizer/src/explorer/ArtifactsView.tsx +162 -0
  49. package/packages/visualizer/src/explorer/Explorer.tsx +47 -3
  50. package/packages/visualizer/src/explorer/RunsView.tsx +43 -19
  51. package/packages/visualizer/src/explorer/catalog.tsx +97 -12
  52. package/packages/visualizer/src/explorer/filters.tsx +279 -0
  53. package/packages/visualizer/src/explorer/search.ts +85 -6
  54. package/packages/visualizer/src/explorer/views.ts +75 -0
@@ -105,6 +105,29 @@ export declare function createReportsApp(deps: ReportsAppDeps): import("hono/hon
105
105
  } | null;
106
106
  parts: readonly {
107
107
  tags?: string[] | undefined;
108
+ scope?: {
109
+ [x: string]: import("hono/utils/types").JSONValue;
110
+ surface?: string | undefined;
111
+ deviceKind?: string | undefined;
112
+ syntheticData?: boolean | undefined;
113
+ dependencies?: string | undefined;
114
+ audioCaptured?: boolean | undefined;
115
+ pacing?: string | undefined;
116
+ asserted?: string[] | undefined;
117
+ limitations?: string[] | undefined;
118
+ declaredBy?: string | undefined;
119
+ verified?: string[] | undefined;
120
+ verifiedBy?: string | undefined;
121
+ provenance?: {
122
+ [x: string]: import("hono/utils/types").JSONValue;
123
+ url: string;
124
+ name?: string | undefined;
125
+ rel?: string | undefined;
126
+ }[] | undefined;
127
+ extensions?: {
128
+ [x: string]: import("hono/utils/types").JSONValue;
129
+ } | undefined;
130
+ } | undefined;
108
131
  attachments?: {
109
132
  [x: string]: import("hono/utils/types").JSONValue;
110
133
  name: string;
@@ -121,6 +144,29 @@ export declare function createReportsApp(deps: ReportsAppDeps): import("hono/hon
121
144
  durationMs?: number | undefined;
122
145
  description?: string | undefined;
123
146
  omitted?: string | undefined;
147
+ scope?: {
148
+ [x: string]: import("hono/utils/types").JSONValue;
149
+ surface?: string | undefined;
150
+ deviceKind?: string | undefined;
151
+ syntheticData?: boolean | undefined;
152
+ dependencies?: string | undefined;
153
+ audioCaptured?: boolean | undefined;
154
+ pacing?: string | undefined;
155
+ asserted?: string[] | undefined;
156
+ limitations?: string[] | undefined;
157
+ declaredBy?: string | undefined;
158
+ verified?: string[] | undefined;
159
+ verifiedBy?: string | undefined;
160
+ provenance?: {
161
+ [x: string]: import("hono/utils/types").JSONValue;
162
+ url: string;
163
+ name?: string | undefined;
164
+ rel?: string | undefined;
165
+ }[] | undefined;
166
+ extensions?: {
167
+ [x: string]: import("hono/utils/types").JSONValue;
168
+ } | undefined;
169
+ } | undefined;
124
170
  tags?: string[] | undefined;
125
171
  at?: {
126
172
  [x: string]: import("hono/utils/types").JSONValue;
@@ -140,6 +186,29 @@ export declare function createReportsApp(deps: ReportsAppDeps): import("hono/hon
140
186
  durationMs?: number | undefined;
141
187
  startedAtMs?: number | undefined;
142
188
  detail?: string | undefined;
189
+ scope?: {
190
+ [x: string]: import("hono/utils/types").JSONValue;
191
+ surface?: string | undefined;
192
+ deviceKind?: string | undefined;
193
+ syntheticData?: boolean | undefined;
194
+ dependencies?: string | undefined;
195
+ audioCaptured?: boolean | undefined;
196
+ pacing?: string | undefined;
197
+ asserted?: string[] | undefined;
198
+ limitations?: string[] | undefined;
199
+ declaredBy?: string | undefined;
200
+ verified?: string[] | undefined;
201
+ verifiedBy?: string | undefined;
202
+ provenance?: {
203
+ [x: string]: import("hono/utils/types").JSONValue;
204
+ url: string;
205
+ name?: string | undefined;
206
+ rel?: string | undefined;
207
+ }[] | undefined;
208
+ extensions?: {
209
+ [x: string]: import("hono/utils/types").JSONValue;
210
+ } | undefined;
211
+ } | undefined;
143
212
  steps?: {
144
213
  [x: string]: import("hono/utils/types").JSONValue;
145
214
  name: string;
@@ -166,6 +235,29 @@ export declare function createReportsApp(deps: ReportsAppDeps): import("hono/hon
166
235
  durationMs?: number | undefined;
167
236
  description?: string | undefined;
168
237
  omitted?: string | undefined;
238
+ scope?: {
239
+ [x: string]: import("hono/utils/types").JSONValue;
240
+ surface?: string | undefined;
241
+ deviceKind?: string | undefined;
242
+ syntheticData?: boolean | undefined;
243
+ dependencies?: string | undefined;
244
+ audioCaptured?: boolean | undefined;
245
+ pacing?: string | undefined;
246
+ asserted?: string[] | undefined;
247
+ limitations?: string[] | undefined;
248
+ declaredBy?: string | undefined;
249
+ verified?: string[] | undefined;
250
+ verifiedBy?: string | undefined;
251
+ provenance?: {
252
+ [x: string]: import("hono/utils/types").JSONValue;
253
+ url: string;
254
+ name?: string | undefined;
255
+ rel?: string | undefined;
256
+ }[] | undefined;
257
+ extensions?: {
258
+ [x: string]: import("hono/utils/types").JSONValue;
259
+ } | undefined;
260
+ } | undefined;
169
261
  tags?: string[] | undefined;
170
262
  at?: {
171
263
  [x: string]: import("hono/utils/types").JSONValue;
@@ -224,6 +316,29 @@ export declare function createReportsApp(deps: ReportsAppDeps): import("hono/hon
224
316
  durationMs?: number | undefined;
225
317
  description?: string | undefined;
226
318
  omitted?: string | undefined;
319
+ scope?: {
320
+ [x: string]: import("hono/utils/types").JSONValue;
321
+ surface?: string | undefined;
322
+ deviceKind?: string | undefined;
323
+ syntheticData?: boolean | undefined;
324
+ dependencies?: string | undefined;
325
+ audioCaptured?: boolean | undefined;
326
+ pacing?: string | undefined;
327
+ asserted?: string[] | undefined;
328
+ limitations?: string[] | undefined;
329
+ declaredBy?: string | undefined;
330
+ verified?: string[] | undefined;
331
+ verifiedBy?: string | undefined;
332
+ provenance?: {
333
+ [x: string]: import("hono/utils/types").JSONValue;
334
+ url: string;
335
+ name?: string | undefined;
336
+ rel?: string | undefined;
337
+ }[] | undefined;
338
+ extensions?: {
339
+ [x: string]: import("hono/utils/types").JSONValue;
340
+ } | undefined;
341
+ } | undefined;
227
342
  tags?: string[] | undefined;
228
343
  at?: {
229
344
  [x: string]: import("hono/utils/types").JSONValue;
@@ -292,6 +407,29 @@ export declare function createReportsApp(deps: ReportsAppDeps): import("hono/hon
292
407
  durationMs?: number | undefined;
293
408
  description?: string | undefined;
294
409
  omitted?: string | undefined;
410
+ scope?: {
411
+ [x: string]: import("hono/utils/types").JSONValue;
412
+ surface?: string | undefined;
413
+ deviceKind?: string | undefined;
414
+ syntheticData?: boolean | undefined;
415
+ dependencies?: string | undefined;
416
+ audioCaptured?: boolean | undefined;
417
+ pacing?: string | undefined;
418
+ asserted?: string[] | undefined;
419
+ limitations?: string[] | undefined;
420
+ declaredBy?: string | undefined;
421
+ verified?: string[] | undefined;
422
+ verifiedBy?: string | undefined;
423
+ provenance?: {
424
+ [x: string]: import("hono/utils/types").JSONValue;
425
+ url: string;
426
+ name?: string | undefined;
427
+ rel?: string | undefined;
428
+ }[] | undefined;
429
+ extensions?: {
430
+ [x: string]: import("hono/utils/types").JSONValue;
431
+ } | undefined;
432
+ } | undefined;
295
433
  tags?: string[] | undefined;
296
434
  at?: {
297
435
  [x: string]: import("hono/utils/types").JSONValue;
@@ -383,6 +521,29 @@ export declare function createReportsApp(deps: ReportsAppDeps): import("hono/hon
383
521
  readonly recordedAtMs: number;
384
522
  readonly checks: readonly {
385
523
  tags?: string[] | undefined;
524
+ scope?: {
525
+ [x: string]: import("hono/utils/types").JSONValue;
526
+ surface?: string | undefined;
527
+ deviceKind?: string | undefined;
528
+ syntheticData?: boolean | undefined;
529
+ dependencies?: string | undefined;
530
+ audioCaptured?: boolean | undefined;
531
+ pacing?: string | undefined;
532
+ asserted?: string[] | undefined;
533
+ limitations?: string[] | undefined;
534
+ declaredBy?: string | undefined;
535
+ verified?: string[] | undefined;
536
+ verifiedBy?: string | undefined;
537
+ provenance?: {
538
+ [x: string]: import("hono/utils/types").JSONValue;
539
+ url: string;
540
+ name?: string | undefined;
541
+ rel?: string | undefined;
542
+ }[] | undefined;
543
+ extensions?: {
544
+ [x: string]: import("hono/utils/types").JSONValue;
545
+ } | undefined;
546
+ } | undefined;
386
547
  attachments?: {
387
548
  [x: string]: import("hono/utils/types").JSONValue;
388
549
  name: string;
@@ -399,6 +560,29 @@ export declare function createReportsApp(deps: ReportsAppDeps): import("hono/hon
399
560
  durationMs?: number | undefined;
400
561
  description?: string | undefined;
401
562
  omitted?: string | undefined;
563
+ scope?: {
564
+ [x: string]: import("hono/utils/types").JSONValue;
565
+ surface?: string | undefined;
566
+ deviceKind?: string | undefined;
567
+ syntheticData?: boolean | undefined;
568
+ dependencies?: string | undefined;
569
+ audioCaptured?: boolean | undefined;
570
+ pacing?: string | undefined;
571
+ asserted?: string[] | undefined;
572
+ limitations?: string[] | undefined;
573
+ declaredBy?: string | undefined;
574
+ verified?: string[] | undefined;
575
+ verifiedBy?: string | undefined;
576
+ provenance?: {
577
+ [x: string]: import("hono/utils/types").JSONValue;
578
+ url: string;
579
+ name?: string | undefined;
580
+ rel?: string | undefined;
581
+ }[] | undefined;
582
+ extensions?: {
583
+ [x: string]: import("hono/utils/types").JSONValue;
584
+ } | undefined;
585
+ } | undefined;
402
586
  tags?: string[] | undefined;
403
587
  at?: {
404
588
  [x: string]: import("hono/utils/types").JSONValue;
@@ -418,6 +602,29 @@ export declare function createReportsApp(deps: ReportsAppDeps): import("hono/hon
418
602
  durationMs?: number | undefined;
419
603
  startedAtMs?: number | undefined;
420
604
  detail?: string | undefined;
605
+ scope?: {
606
+ [x: string]: import("hono/utils/types").JSONValue;
607
+ surface?: string | undefined;
608
+ deviceKind?: string | undefined;
609
+ syntheticData?: boolean | undefined;
610
+ dependencies?: string | undefined;
611
+ audioCaptured?: boolean | undefined;
612
+ pacing?: string | undefined;
613
+ asserted?: string[] | undefined;
614
+ limitations?: string[] | undefined;
615
+ declaredBy?: string | undefined;
616
+ verified?: string[] | undefined;
617
+ verifiedBy?: string | undefined;
618
+ provenance?: {
619
+ [x: string]: import("hono/utils/types").JSONValue;
620
+ url: string;
621
+ name?: string | undefined;
622
+ rel?: string | undefined;
623
+ }[] | undefined;
624
+ extensions?: {
625
+ [x: string]: import("hono/utils/types").JSONValue;
626
+ } | undefined;
627
+ } | undefined;
421
628
  steps?: {
422
629
  [x: string]: import("hono/utils/types").JSONValue;
423
630
  name: string;
@@ -444,6 +651,29 @@ export declare function createReportsApp(deps: ReportsAppDeps): import("hono/hon
444
651
  durationMs?: number | undefined;
445
652
  description?: string | undefined;
446
653
  omitted?: string | undefined;
654
+ scope?: {
655
+ [x: string]: import("hono/utils/types").JSONValue;
656
+ surface?: string | undefined;
657
+ deviceKind?: string | undefined;
658
+ syntheticData?: boolean | undefined;
659
+ dependencies?: string | undefined;
660
+ audioCaptured?: boolean | undefined;
661
+ pacing?: string | undefined;
662
+ asserted?: string[] | undefined;
663
+ limitations?: string[] | undefined;
664
+ declaredBy?: string | undefined;
665
+ verified?: string[] | undefined;
666
+ verifiedBy?: string | undefined;
667
+ provenance?: {
668
+ [x: string]: import("hono/utils/types").JSONValue;
669
+ url: string;
670
+ name?: string | undefined;
671
+ rel?: string | undefined;
672
+ }[] | undefined;
673
+ extensions?: {
674
+ [x: string]: import("hono/utils/types").JSONValue;
675
+ } | undefined;
676
+ } | undefined;
447
677
  tags?: string[] | undefined;
448
678
  at?: {
449
679
  [x: string]: import("hono/utils/types").JSONValue;
@@ -502,6 +732,29 @@ export declare function createReportsApp(deps: ReportsAppDeps): import("hono/hon
502
732
  durationMs?: number | undefined;
503
733
  description?: string | undefined;
504
734
  omitted?: string | undefined;
735
+ scope?: {
736
+ [x: string]: import("hono/utils/types").JSONValue;
737
+ surface?: string | undefined;
738
+ deviceKind?: string | undefined;
739
+ syntheticData?: boolean | undefined;
740
+ dependencies?: string | undefined;
741
+ audioCaptured?: boolean | undefined;
742
+ pacing?: string | undefined;
743
+ asserted?: string[] | undefined;
744
+ limitations?: string[] | undefined;
745
+ declaredBy?: string | undefined;
746
+ verified?: string[] | undefined;
747
+ verifiedBy?: string | undefined;
748
+ provenance?: {
749
+ [x: string]: import("hono/utils/types").JSONValue;
750
+ url: string;
751
+ name?: string | undefined;
752
+ rel?: string | undefined;
753
+ }[] | undefined;
754
+ extensions?: {
755
+ [x: string]: import("hono/utils/types").JSONValue;
756
+ } | undefined;
757
+ } | undefined;
505
758
  tags?: string[] | undefined;
506
759
  at?: {
507
760
  [x: string]: import("hono/utils/types").JSONValue;
@@ -570,6 +823,29 @@ export declare function createReportsApp(deps: ReportsAppDeps): import("hono/hon
570
823
  durationMs?: number | undefined;
571
824
  description?: string | undefined;
572
825
  omitted?: string | undefined;
826
+ scope?: {
827
+ [x: string]: import("hono/utils/types").JSONValue;
828
+ surface?: string | undefined;
829
+ deviceKind?: string | undefined;
830
+ syntheticData?: boolean | undefined;
831
+ dependencies?: string | undefined;
832
+ audioCaptured?: boolean | undefined;
833
+ pacing?: string | undefined;
834
+ asserted?: string[] | undefined;
835
+ limitations?: string[] | undefined;
836
+ declaredBy?: string | undefined;
837
+ verified?: string[] | undefined;
838
+ verifiedBy?: string | undefined;
839
+ provenance?: {
840
+ [x: string]: import("hono/utils/types").JSONValue;
841
+ url: string;
842
+ name?: string | undefined;
843
+ rel?: string | undefined;
844
+ }[] | undefined;
845
+ extensions?: {
846
+ [x: string]: import("hono/utils/types").JSONValue;
847
+ } | undefined;
848
+ } | undefined;
573
849
  tags?: string[] | undefined;
574
850
  at?: {
575
851
  [x: string]: import("hono/utils/types").JSONValue;
@@ -1,3 +1,4 @@
1
+ import { type ArtifactFilter, type FilterFields, type FilterName, type QueryFacets } from './artifact-query.js';
1
2
  import type { EvidenceStatus, SpecEntry } from './explorer-types.js';
2
3
  import type { EvidenceSummary, LayerEvidence, LayerHeader, LayerRow, StatusCounts, VideoCounts, VideoRef } from './evidence-types.js';
3
4
  import { RecordedSpecs } from './recorded-specs.js';
@@ -98,6 +99,8 @@ export type TestSummary = {
98
99
  readonly artifactTags?: readonly string[];
99
100
  /** 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. */
100
101
  readonly inheritedArtifactTags?: readonly string[];
102
+ /** 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. */
103
+ readonly scopeProfiles?: readonly (readonly string[])[];
101
104
  readonly labels: Readonly<Record<string, string>>;
102
105
  /** What its current results carry: attachments, steps, retries, and the kinds of attachment. */
103
106
  readonly evidence: {
@@ -171,6 +174,8 @@ export type CatalogFacets = {
171
174
  readonly tags: readonly FacetValue[];
172
175
  /** The tags of attachments, normalized. Absent in documents of older servers. */
173
176
  readonly artifactTags?: readonly FacetValue[];
177
+ /** 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. */
178
+ readonly scopes?: readonly FacetValue[];
174
179
  readonly branches: readonly FacetValue[];
175
180
  readonly labels: Readonly<Record<string, readonly FacetValue[]>>;
176
181
  };
@@ -194,6 +199,8 @@ export type TestQuery = {
194
199
  readonly artifactTag?: readonly string[];
195
200
  /** 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. */
196
201
  readonly inherited?: boolean;
202
+ /** 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. */
203
+ readonly scope?: readonly string[];
197
204
  /** Only tests recorded in this run (a key from `runList`). */
198
205
  readonly run?: string;
199
206
  readonly sort?: TestSort;
@@ -263,12 +270,20 @@ export type RunSummary = {
263
270
  * and references that cannot be played. Absent when it has none, and in a report that carries no video information.
264
271
  */
265
272
  readonly videos?: VideoCounts;
273
+ /** The normalized tags of the attachments of its results and layers, sorted and distinct. Absent when none is tagged. */
274
+ readonly artifactTags?: readonly string[];
275
+ /** The tags of its checks and layers, which its attachments inherit (what `inherited=1` also matches), sorted and distinct. Absent when none. */
276
+ readonly tags?: readonly string[];
277
+ /** The media types (roles) of its attachments, sorted and distinct; `video` only for a playable recording. Absent when it has no attachment. */
278
+ readonly types?: readonly string[];
279
+ /** The `key=value` labels of its results and layers, sorted and distinct. Absent when none. */
280
+ readonly labels?: readonly string[];
266
281
  };
267
- /** What `runList` can be asked. */
268
- export type RunQuery = {
269
- /** Only runs that have at least one playable video recording. `total` then counts only those. */
270
- readonly video?: boolean;
271
- };
282
+ /**
283
+ * What `runList` can be asked: an {@link ArtifactFilter}, of which a run answers every dimension but `domain` and
284
+ * `spec`. `video` keeps only runs that have at least one playable video recording; `total` then counts only those.
285
+ */
286
+ export type RunQuery = ArtifactFilter;
272
287
  /** `GET /api/reports/runs`: runs, newest first. */
273
288
  export type RunList = {
274
289
  readonly schemaVersion: typeof TEST_CATALOG_VERSION;
@@ -278,6 +293,10 @@ export type RunList = {
278
293
  readonly runs: readonly RunSummary[];
279
294
  /** How many of all runs (whatever was asked, before `limit`) have a playable video recording. */
280
295
  readonly withVideo?: number;
296
+ /** The filters of the query that were applied. A viewer that asked for another knows the server ignored it. */
297
+ readonly filters?: readonly FilterName[];
298
+ /** The values each filter dimension offers with counts over every run (see `facetsOf`). */
299
+ readonly facets?: QueryFacets;
281
300
  };
282
301
  /** One test's last result within one run. */
283
302
  export type RunResult = {
@@ -302,6 +321,60 @@ export type RunDetail = {
302
321
  readonly run: RunSummary;
303
322
  readonly results: readonly RunResult[];
304
323
  };
324
+ /**
325
+ * One recorded result that carries evidence (an attachment: a recording, a screenshot, a trace), with where and when it
326
+ * was recorded: the unit `artifactList` finds. It is a result in the history, not only the report's current winner, so
327
+ * a recording tagged `demo` is found with its original run, revision and status even when a later attempt is untagged.
328
+ */
329
+ export type ArtifactResult = {
330
+ /** The test's key, as in `TestSummary.key`. */
331
+ readonly key: string;
332
+ readonly id: string;
333
+ readonly title: string;
334
+ readonly specPath: string | null;
335
+ /** The domain of its spec, or null when it has none. */
336
+ readonly domain: string | null;
337
+ /** When it was recorded, epoch ms. */
338
+ readonly at: number;
339
+ readonly status: EvidenceStatus;
340
+ readonly environment: string | null;
341
+ readonly producer: string;
342
+ /** The kind of the part that held it. */
343
+ readonly kind: string;
344
+ readonly parameters: Readonly<Record<string, string>>;
345
+ readonly branch: string | null;
346
+ readonly sha: string | null;
347
+ /** The key of its run (see `runKey`). */
348
+ readonly run: string;
349
+ readonly runUrl: string | null;
350
+ /** The id of the layer that recorded it: with `key`, the exact result to open. */
351
+ readonly layer: string;
352
+ readonly backend: string;
353
+ /** Attachments on the result, including those on its steps and attempts. */
354
+ readonly attachments: number;
355
+ /** The normalized tags of its attachments. */
356
+ readonly artifactTags: readonly string[];
357
+ /** The tags of its check and run, which its attachments inherit. */
358
+ readonly tags: readonly string[];
359
+ /** The media types (roles) of its attachments; `video` only for a playable recording. */
360
+ readonly types: readonly string[];
361
+ readonly labels: Readonly<Record<string, string>>;
362
+ readonly videos?: readonly VideoRef[];
363
+ };
364
+ /** `GET /api/reports/artifacts`: the recorded results that carry evidence and match a query, newest first. */
365
+ export type ArtifactList = {
366
+ readonly schemaVersion: typeof TEST_CATALOG_VERSION;
367
+ readonly version: string;
368
+ /** Results that matched, before `offset` and `limit`. */
369
+ readonly total: number;
370
+ readonly offset: number;
371
+ readonly limit: number;
372
+ readonly results: readonly ArtifactResult[];
373
+ /** The filters of the query that were applied. */
374
+ readonly filters: readonly FilterName[];
375
+ /** The values each dimension offers with disjunctive counts over every result that carries evidence. */
376
+ readonly facets: QueryFacets;
377
+ };
305
378
  /**
306
379
  * The run a layer belongs to: the execution its producer named (`run:<id>`), else its run URL, else `branch@sha`, else
307
380
  * `local`. Layers with the same key are one run. Only grouping: it is never part of the slot a result fills.
@@ -351,6 +424,14 @@ export declare class TestHistory {
351
424
  readonly run: RunSummary;
352
425
  readonly results: RunResult[];
353
426
  } | null;
427
+ /**
428
+ * Every recorded result that carries an attachment, newest first, with the values a query looks at. Built once per
429
+ * layer added. The domain is left to the caller (`artifactList`), which may know more specs than the layers recorded.
430
+ */
431
+ evidenceResults(): readonly {
432
+ readonly result: ArtifactResult;
433
+ readonly fields: FilterFields;
434
+ }[];
354
435
  /** The keys of the tests one run recorded. */
355
436
  testsIn(key: string): ReadonlySet<string>;
356
437
  }
@@ -403,9 +484,28 @@ export declare function hasVideo(run: Pick<RunSummary, 'videos'>): boolean;
403
484
  * any recording's bytes. `withVideo` always counts every run that has one.
404
485
  */
405
486
  export declare function runList(history: TestHistory, version: string, limit?: number, query?: RunQuery): RunList;
487
+ /** What a query looks at in a run: see {@link ArtifactFilter}. A run's outcome is `failed` when any result failed. */
488
+ export declare function fieldsOfRun(run: RunSummary): FilterFields;
489
+ /**
490
+ * The recorded results that carry evidence and match a query, newest first, across the whole history (`limit` default
491
+ * 100). `total` and the facet counts apply to every match before `offset` and `limit`. `domainOf` tells the domain of a
492
+ * spec path when the caller knows more than the layers recorded (a server with a spec set); otherwise it is the
493
+ * recorded spec's domain, else the folder the path implies.
494
+ *
495
+ * @example
496
+ * ```ts
497
+ * const list = artifactList(history, 'v1', { artifactTag: ['demo'], type: ['video'], environment: ['ios'] });
498
+ * list.results.map(r => [r.title, r.run, r.sha, r.status]);
499
+ * ```
500
+ */
501
+ export declare function artifactList(history: TestHistory, version: string, query?: ArtifactFilter, page?: {
502
+ readonly limit?: number;
503
+ readonly offset?: number;
504
+ readonly domainOf?: (specPath: string) => string | undefined;
505
+ }): ArtifactList;
406
506
  /** One run with every result it recorded, or null when no run has that key. */
407
507
  export declare function runDetail(history: TestHistory, version: string, key: string): RunDetail | null;
408
- /** 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`. */
508
+ /** 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`. */
409
509
  export declare function parseTestQuery(params: URLSearchParams): TestQuery;
410
510
  /** A duration for a person: `850 ms`, `12.3 s`, `4m 05s`. */
411
511
  export declare function formatMs(ms: number | null | undefined): string;
@@ -415,5 +515,7 @@ export declare function catalogMarkdown(catalog: TestCatalog): string;
415
515
  export declare function historyMarkdown(document: TestHistoryDocument): string;
416
516
  /** The run list as Markdown. */
417
517
  export declare function runsMarkdown(list: RunList): string;
518
+ /** The artifact results as Markdown, for an agent or a terminal. */
519
+ export declare function artifactsMarkdown(list: ArtifactList): string;
418
520
  /** One run as Markdown. */
419
521
  export declare function runMarkdown(detail: RunDetail): string;