@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
|
@@ -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;
|
|
@@ -99,6 +99,8 @@ export type TestSummary = {
|
|
|
99
99
|
readonly artifactTags?: readonly string[];
|
|
100
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. */
|
|
101
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[])[];
|
|
102
104
|
readonly labels: Readonly<Record<string, string>>;
|
|
103
105
|
/** What its current results carry: attachments, steps, retries, and the kinds of attachment. */
|
|
104
106
|
readonly evidence: {
|
|
@@ -172,6 +174,8 @@ export type CatalogFacets = {
|
|
|
172
174
|
readonly tags: readonly FacetValue[];
|
|
173
175
|
/** The tags of attachments, normalized. Absent in documents of older servers. */
|
|
174
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[];
|
|
175
179
|
readonly branches: readonly FacetValue[];
|
|
176
180
|
readonly labels: Readonly<Record<string, readonly FacetValue[]>>;
|
|
177
181
|
};
|
|
@@ -195,6 +199,8 @@ export type TestQuery = {
|
|
|
195
199
|
readonly artifactTag?: readonly string[];
|
|
196
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. */
|
|
197
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[];
|
|
198
204
|
/** Only tests recorded in this run (a key from `runList`). */
|
|
199
205
|
readonly run?: string;
|
|
200
206
|
readonly sort?: TestSort;
|
|
@@ -264,6 +270,11 @@ export type RunSummary = {
|
|
|
264
270
|
* and references that cannot be played. Absent when it has none, and in a report that carries no video information.
|
|
265
271
|
*/
|
|
266
272
|
readonly videos?: VideoCounts;
|
|
273
|
+
/**
|
|
274
|
+
* Files attached to the run itself, in its layers (and their parts), not to any one check: review movies, archives,
|
|
275
|
+
* summaries. All kinds, videos included. Absent when none, and in a report that carries no such information.
|
|
276
|
+
*/
|
|
277
|
+
readonly attachments?: number;
|
|
267
278
|
/** The normalized tags of the attachments of its results and layers, sorted and distinct. Absent when none is tagged. */
|
|
268
279
|
readonly artifactTags?: readonly string[];
|
|
269
280
|
/** The tags of its checks and layers, which its attachments inherit (what `inherited=1` also matches), sorted and distinct. Absent when none. */
|
|
@@ -308,12 +319,33 @@ export type RunResult = {
|
|
|
308
319
|
/** Every video its evidence references, with who holds it (check, retry attempt or step). Absent when there is none. */
|
|
309
320
|
readonly videos?: readonly VideoRef[];
|
|
310
321
|
};
|
|
322
|
+
/**
|
|
323
|
+
* One layer of a run, as the run's artifact hub names it: who recorded it, how many checks it holds (zero is possible
|
|
324
|
+
* and is not a pass) and the files it carries as a whole. Read the files with `GET /evidence/detail/{backend}/{id}`.
|
|
325
|
+
*/
|
|
326
|
+
export type RunLayer = {
|
|
327
|
+
/** The layer's id: 64 hex digits for an archived layer. */
|
|
328
|
+
readonly id: string;
|
|
329
|
+
/** The backend it was read from, for `GET /evidence/detail/{backend}/{id}`. */
|
|
330
|
+
readonly backend: string;
|
|
331
|
+
readonly producer: string;
|
|
332
|
+
readonly environment: string | null;
|
|
333
|
+
readonly recordedAtMs: number;
|
|
334
|
+
/** How many checks the layer recorded. A layer of zero checks is evidence only: it is not a passing test. */
|
|
335
|
+
readonly checks: number;
|
|
336
|
+
/** Files the layer carries as a whole (not those of its checks). */
|
|
337
|
+
readonly attachments: number;
|
|
338
|
+
/** Its videos, when it has any. */
|
|
339
|
+
readonly videos?: VideoCounts;
|
|
340
|
+
};
|
|
311
341
|
/** `GET /api/reports/runs/detail`: one run and every result it recorded. */
|
|
312
342
|
export type RunDetail = {
|
|
313
343
|
readonly schemaVersion: typeof TEST_CATALOG_VERSION;
|
|
314
344
|
readonly version: string;
|
|
315
345
|
readonly run: RunSummary;
|
|
316
346
|
readonly results: readonly RunResult[];
|
|
347
|
+
/** 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. */
|
|
348
|
+
readonly layers?: readonly RunLayer[];
|
|
317
349
|
};
|
|
318
350
|
/**
|
|
319
351
|
* One recorded result that carries evidence (an attachment: a recording, a screenshot, a trace), with where and when it
|
|
@@ -417,6 +449,7 @@ export declare class TestHistory {
|
|
|
417
449
|
run(key: string): {
|
|
418
450
|
readonly run: RunSummary;
|
|
419
451
|
readonly results: RunResult[];
|
|
452
|
+
readonly layers: RunLayer[];
|
|
420
453
|
} | null;
|
|
421
454
|
/**
|
|
422
455
|
* Every recorded result that carries an attachment, newest first, with the values a query looks at. Built once per
|
|
@@ -499,7 +532,7 @@ export declare function artifactList(history: TestHistory, version: string, quer
|
|
|
499
532
|
}): ArtifactList;
|
|
500
533
|
/** One run with every result it recorded, or null when no run has that key. */
|
|
501
534
|
export declare function runDetail(history: TestHistory, version: string, key: string): RunDetail | null;
|
|
502
|
-
/** 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`. */
|
|
535
|
+
/** 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`. */
|
|
503
536
|
export declare function parseTestQuery(params: URLSearchParams): TestQuery;
|
|
504
537
|
/** A duration for a person: `850 ms`, `12.3 s`, `4m 05s`. */
|
|
505
538
|
export declare function formatMs(ms: number | null | undefined): string;
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import { displayTag, normalizeTag, normalizeTags } from './artifact-tags.js';
|
|
26
26
|
import { facetsOf, forRuns, labelList, matchesFilter, outcomeOfCounts, splitList, usedFilters } from './artifact-query.js';
|
|
27
27
|
import { videoCounts } from './evidence-detail.js';
|
|
28
|
+
import { matchesScope, scopeTokens, UNKNOWN_SCOPE } from './evidence-scope.js';
|
|
28
29
|
import { checkSlots } from './evidence-view.js';
|
|
29
30
|
import { combineEvidence, emptyCounts, slotKey, specPath as normalSpecPath } from './layer-merge.js';
|
|
30
31
|
import { RecordedSpecs } from './recorded-specs.js';
|
|
@@ -131,7 +132,7 @@ export class TestHistory {
|
|
|
131
132
|
const key = runKey(layer);
|
|
132
133
|
let run = this.#runs.get(key);
|
|
133
134
|
if (!run) {
|
|
134
|
-
run = { key, runUrl: layer.runUrl, branch: layer.branch, sha: layer.sha, start: layer.recordedAtMs, end: layer.recordedAtMs, firstAt: layer.recordedAtMs, first: layer.id, producers: new Set(), environments: new Set(), layers: 0, runId: layer.run?.id, title: undefined, titledAt: 0, titledBy: '', freshness: undefined, videos: [], evidence: { tags: new Set(), artifactTags: new Set(), roles: new Set(), labels: new Set() } };
|
|
135
|
+
run = { key, runUrl: layer.runUrl, branch: layer.branch, sha: layer.sha, start: layer.recordedAtMs, end: layer.recordedAtMs, firstAt: layer.recordedAtMs, first: layer.id, producers: new Set(), environments: new Set(), layers: 0, runId: layer.run?.id, title: undefined, titledAt: 0, titledBy: '', freshness: undefined, videos: [], attachments: 0, members: [], evidence: { tags: new Set(), artifactTags: new Set(), roles: new Set(), labels: new Set() } };
|
|
135
136
|
this.#runs.set(key, run);
|
|
136
137
|
}
|
|
137
138
|
// A run is told by its first layer (by time, then id), whatever order its layers arrive in.
|
|
@@ -160,6 +161,10 @@ export class TestHistory {
|
|
|
160
161
|
run.layers += 1;
|
|
161
162
|
if (layer.evidence?.videos)
|
|
162
163
|
run.videos.push(...layer.evidence.videos);
|
|
164
|
+
const held = layer.evidence?.attachments ?? 0;
|
|
165
|
+
run.attachments += held;
|
|
166
|
+
const layerVideos = videoCounts(layer.evidence?.videos);
|
|
167
|
+
run.members.push({ id: layer.id, backend, producer: layer.producer, environment: layer.environment, recordedAtMs: layer.recordedAtMs, checks: rows.length, attachments: held, ...(layerVideos ? { videos: layerVideos } : {}) });
|
|
163
168
|
if (layer.evidence)
|
|
164
169
|
takeEvidence(run.evidence, layer.evidence);
|
|
165
170
|
const origin = originOf(layer, backend);
|
|
@@ -252,7 +257,8 @@ export class TestHistory {
|
|
|
252
257
|
...(r.e?.videos ? { videos: r.e.videos } : {}),
|
|
253
258
|
}));
|
|
254
259
|
results.sort((a, b) => FAILURE_RANK[b.status] - FAILURE_RANK[a.status] || compareText(a.specPath ?? '', b.specPath ?? '') || compareText(a.title, b.title) || compareText(a.environment ?? '', b.environment ?? '') || compareText(a.producer, b.producer) || compareText(a.key, b.key));
|
|
255
|
-
|
|
260
|
+
const layers = [...run.members].sort((a, b) => a.recordedAtMs - b.recordedAtMs || compareText(a.id, b.id));
|
|
261
|
+
return { run: runSummary(run, cells), results, layers };
|
|
256
262
|
}
|
|
257
263
|
/**
|
|
258
264
|
* Every recorded result that carries an attachment, newest first, with the values a query looks at. Built once per
|
|
@@ -351,6 +357,7 @@ function runSummary(run, cells) {
|
|
|
351
357
|
producers: [...run.producers].sort(), environments: [...run.environments].sort(),
|
|
352
358
|
layers: run.layers, tests: tests.size, counts, totalDurationMs: measured ? total : null, failing,
|
|
353
359
|
...(video ? { videos: video } : {}),
|
|
360
|
+
...(run.attachments ? { attachments: run.attachments } : {}),
|
|
354
361
|
...(carried.artifactTags.size ? { artifactTags: [...carried.artifactTags].sort() } : {}),
|
|
355
362
|
...(carried.roles.size ? { types: typesOf(carried.roles, (video?.recordings ?? 0) > 0) } : {}),
|
|
356
363
|
...(carried.labels.size ? { labels: [...carried.labels].sort() } : {}),
|
|
@@ -448,10 +455,16 @@ function summaryOf(slot, history, known) {
|
|
|
448
455
|
const labels = {};
|
|
449
456
|
let attachments = 0, steps = 0, attempts = 0;
|
|
450
457
|
const roles = new Set();
|
|
458
|
+
const profiles = new Map();
|
|
451
459
|
for (const [at, outcome] of outcomes.entries()) {
|
|
452
460
|
const evidence = outcome.evidence;
|
|
453
461
|
if (!evidence)
|
|
454
462
|
continue;
|
|
463
|
+
for (const video of evidence.videos ?? [])
|
|
464
|
+
if (video.available) {
|
|
465
|
+
const tokens = scopeTokens(video.scope);
|
|
466
|
+
profiles.set(tokens.join('\n'), tokens);
|
|
467
|
+
}
|
|
455
468
|
if (evidence.attachments > 0)
|
|
456
469
|
for (const tag of normalizeTags([...evidence.tags, ...(slot.outcomes[at]?.layer.evidence?.tags ?? [])]))
|
|
457
470
|
inheritedTags.add(tag);
|
|
@@ -481,6 +494,7 @@ function summaryOf(slot, history, known) {
|
|
|
481
494
|
tags: tagList,
|
|
482
495
|
...(artifactTags.size ? { artifactTags: [...artifactTags].sort() } : {}),
|
|
483
496
|
...([...inheritedTags].some(tag => !artifactTags.has(tag)) ? { inheritedArtifactTags: [...inheritedTags].filter(tag => !artifactTags.has(tag)).sort() } : {}),
|
|
497
|
+
...(profiles.size ? { scopeProfiles: [...profiles.values()] } : {}),
|
|
484
498
|
labels,
|
|
485
499
|
evidence: { attachments, steps, attempts, roles: [...roles].sort() },
|
|
486
500
|
lastRecordedAtMs: slot.latestAtMs,
|
|
@@ -490,7 +504,7 @@ function summaryOf(slot, history, known) {
|
|
|
490
504
|
/** The lowercase text a search matches a test against: title, id, spec, domain, tags, labels, environments and producers. */
|
|
491
505
|
export function haystackOf(test) {
|
|
492
506
|
return [
|
|
493
|
-
test.title, test.id, test.specPath ?? '', test.spec?.title ?? '', test.spec?.domain ?? '', ...test.tags, ...(test.artifactTags ?? []).map(displayTag),
|
|
507
|
+
test.title, test.id, test.specPath ?? '', test.spec?.title ?? '', test.spec?.domain ?? '', ...test.tags, ...(test.artifactTags ?? []).map(displayTag), ...(test.scopeProfiles ?? []).flat().filter(token => token !== UNKNOWN_SCOPE),
|
|
494
508
|
...Object.entries(test.labels).flatMap(([key, value]) => [key, value]), ...test.environments, ...test.producers,
|
|
495
509
|
].join('\n').toLowerCase();
|
|
496
510
|
}
|
|
@@ -597,6 +611,7 @@ export function catalogModel(layers, sources = {}) {
|
|
|
597
611
|
specs: [...specGroups].map(([, list]) => { const g = group(list[0].spec.path, list); return { ...list[0].spec, tests: g.tests, counts: g.counts }; }).sort((a, b) => compareText(a.domain, b.domain) || compareText(a.title, b.title)),
|
|
598
612
|
tags: facet(tests, test => test.tags),
|
|
599
613
|
artifactTags: facet(tests, test => test.artifactTags ?? []),
|
|
614
|
+
scopes: facet(tests, test => (test.scopeProfiles ?? []).flat()),
|
|
600
615
|
branches: facet(tests, test => test.stats.branches),
|
|
601
616
|
labels: Object.fromEntries([...labelKeys].sort().map(key => [key, facet(tests, test => (test.labels[key] === undefined ? [] : [test.labels[key]]))])),
|
|
602
617
|
};
|
|
@@ -633,6 +648,8 @@ export function matchesQuery(model, test, query, inRun) {
|
|
|
633
648
|
return false;
|
|
634
649
|
if (query.artifactTag?.length && !query.artifactTag.some(tag => { const wanted = normalizeTag(tag); return wanted !== null && ((test.artifactTags?.includes(wanted) ?? false) || (query.inherited === true && (test.inheritedArtifactTags?.includes(wanted) ?? false))); }))
|
|
635
650
|
return false;
|
|
651
|
+
if (query.scope?.length && !(test.scopeProfiles ?? []).some(tokens => matchesScope(tokens, query.scope)))
|
|
652
|
+
return false;
|
|
636
653
|
if (inRun && !inRun.has(test.key))
|
|
637
654
|
return false;
|
|
638
655
|
return true;
|
|
@@ -725,7 +742,7 @@ export function runDetail(history, version, key) {
|
|
|
725
742
|
const found = history.run(key);
|
|
726
743
|
return found ? { schemaVersion: TEST_CATALOG_VERSION, version, ...found } : null;
|
|
727
744
|
}
|
|
728
|
-
/** 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`. */
|
|
745
|
+
/** 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`. */
|
|
729
746
|
export function parseTestQuery(params) {
|
|
730
747
|
const many = (name) => params.getAll(name).flatMap(splitList);
|
|
731
748
|
const number = (name) => { const value = params.get(name); return value !== null && /^\d+$/.test(value) ? Number(value) : undefined; };
|
|
@@ -734,7 +751,7 @@ export function parseTestQuery(params) {
|
|
|
734
751
|
const q = params.get('q');
|
|
735
752
|
if (q)
|
|
736
753
|
query.q = q;
|
|
737
|
-
for (const name of ['status', 'environment', 'producer', 'domain', 'spec', 'tag', 'artifactTag']) {
|
|
754
|
+
for (const name of ['status', 'environment', 'producer', 'domain', 'spec', 'tag', 'artifactTag', 'scope']) {
|
|
738
755
|
const values = many(name);
|
|
739
756
|
if (values.length)
|
|
740
757
|
query[name] = values;
|
|
@@ -815,6 +832,7 @@ export function historyMarkdown(document) {
|
|
|
815
832
|
`- Duration: ${d ? `avg ${formatMs(d.avgMs)}, p50 ${formatMs(d.p50Ms)}, p95 ${formatMs(d.p95Ms)}, min ${formatMs(d.minMs)}, max ${formatMs(d.maxMs)}, last ${formatMs(d.lastMs)}` : 'not measured'}`,
|
|
816
833
|
...(test.tags.length ? [`- Tags: ${test.tags.join(', ')}`] : []),
|
|
817
834
|
...(test.artifactTags?.length ? [`- Artifact tags: ${test.artifactTags.map(displayTag).join(', ')}`] : []),
|
|
835
|
+
...(test.scopeProfiles?.length ? [`- Recording scope: ${test.scopeProfiles.map(tokens => tokens.join(' ')).join('; ')}`] : []),
|
|
818
836
|
...(Object.keys(test.labels).length ? [`- Labels: ${Object.entries(test.labels).map(([k, v]) => `${k}=${v}`).join(', ')}`] : []),
|
|
819
837
|
`- Evidence: ${test.evidence.attachments} attachments${test.evidence.roles.length ? ` (${test.evidence.roles.join(', ')})` : ''}, ${test.evidence.steps} steps`,
|
|
820
838
|
'',
|