@crvouga/atlas 1.0.0 → 1.2.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 (45) 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.js +2 -1
  5. package/dist/reports/src/evidence-types.d.ts +2 -0
  6. package/dist/reports/src/test-catalog.d.ts +101 -5
  7. package/dist/reports/src/test-catalog.js +140 -5
  8. package/dist/schema/src/api.d.ts +225 -0
  9. package/dist/schema/src/api.js +41 -2
  10. package/dist/server/src/app.js +16 -5
  11. package/dist/server/src/openapi.js +41 -3
  12. package/docs/reference/http-api.md +84 -2
  13. package/docs/reference/reports.md +371 -7
  14. package/docs/reference/schema.md +32 -0
  15. package/docs/rich-evidence.md +31 -0
  16. package/docs/server.md +4 -1
  17. package/docs/ui.md +8 -1
  18. package/llms-full.txt +127 -4
  19. package/package.json +1 -1
  20. package/packages/schema/schemas/effective-delta.schema.json +14 -0
  21. package/packages/schema/schemas/effective-evidence.schema.json +14 -0
  22. package/packages/schema/schemas/evidence-page.schema.json +7 -0
  23. package/packages/schema/schemas/layer-rows.schema.json +7 -0
  24. package/packages/schema/schemas/report-run.schema.json +35 -0
  25. package/packages/schema/schemas/report-runs.schema.json +221 -0
  26. package/packages/schema/schemas/test-catalog.schema.json +7 -0
  27. package/packages/schema/schemas/test-history.schema.json +14 -0
  28. package/packages/server/openapi.json +2473 -1035
  29. package/packages/visualizer/src/evidence/check-detail.tsx +6 -1
  30. package/packages/visualizer/src/evidence/evidence-context.tsx +9 -2
  31. package/packages/visualizer/src/evidence/load-state.tsx +65 -0
  32. package/packages/visualizer/src/evidence/overview.tsx +4 -6
  33. package/packages/visualizer/src/evidence/recording-page.tsx +17 -7
  34. package/packages/visualizer/src/evidence/spec-header.tsx +8 -6
  35. package/packages/visualizer/src/evidence/spec-page.tsx +98 -15
  36. package/packages/visualizer/src/explorer/ArtifactsView.tsx +162 -0
  37. package/packages/visualizer/src/explorer/Explorer.tsx +28 -3
  38. package/packages/visualizer/src/explorer/RunsView.tsx +43 -19
  39. package/packages/visualizer/src/explorer/catalog.tsx +97 -12
  40. package/packages/visualizer/src/explorer/filters.tsx +279 -0
  41. package/packages/visualizer/src/explorer/search.ts +81 -6
  42. package/packages/visualizer/src/explorer/views.ts +75 -0
  43. package/packages/visualizer/src/find/Finder.tsx +45 -7
  44. package/packages/visualizer/src/spec-kind.test.ts +14 -1
  45. package/packages/visualizer/src/spec-kind.ts +28 -2
@@ -10,7 +10,9 @@
10
10
  * always the report's winner (`combineEvidence`), and history only describes how it got there.
11
11
  * - `testCatalog` turns the report's winners (plus a history, when there is one) into a `TestCatalog`: a summary with
12
12
  * insights (slowest, flaky, failing), facets to filter by, and the tests that match a `TestQuery`.
13
- * - `testHistory`, `runList` and `runDetail` answer for one test, every run, and one run.
13
+ * - `testHistory`, `runList` and `runDetail` answer for one test, every run, and one run. `runList` and `artifactList`
14
+ * narrow by tag, label, media type, environment, branch, outcome and time (see `artifact-query.ts`); `artifactList` finds
15
+ * the recorded results that carry evidence across the whole history, not only the latest winners.
14
16
  * - `catalogMarkdown`, `historyMarkdown`, `runsMarkdown` and `runMarkdown` say the same in Markdown, for an agent or a
15
17
  * terminal.
16
18
  *
@@ -21,6 +23,7 @@
21
23
  * @see docs/server.md#reading-the-report-as-an-agent
22
24
  */
23
25
  import { displayTag, normalizeTag, normalizeTags } from './artifact-tags.js';
26
+ import { facetsOf, forRuns, labelList, matchesFilter, outcomeOfCounts, splitList, usedFilters } from './artifact-query.js';
24
27
  import { videoCounts } from './evidence-detail.js';
25
28
  import { checkSlots } from './evidence-view.js';
26
29
  import { combineEvidence, emptyCounts, slotKey, specPath as normalSpecPath } from './layer-merge.js';
@@ -101,6 +104,7 @@ export class TestHistory {
101
104
  #seen = new Set();
102
105
  #rev = 0;
103
106
  #cells = null;
107
+ #evidence = null;
104
108
  /** Changes each time a layer is added. */
105
109
  get rev() {
106
110
  return this.#rev;
@@ -127,7 +131,7 @@ export class TestHistory {
127
131
  const key = runKey(layer);
128
132
  let run = this.#runs.get(key);
129
133
  if (!run) {
130
- 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: [] };
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() } };
131
135
  this.#runs.set(key, run);
132
136
  }
133
137
  // A run is told by its first layer (by time, then id), whatever order its layers arrive in.
@@ -156,6 +160,8 @@ export class TestHistory {
156
160
  run.layers += 1;
157
161
  if (layer.evidence?.videos)
158
162
  run.videos.push(...layer.evidence.videos);
163
+ if (layer.evidence)
164
+ takeEvidence(run.evidence, layer.evidence);
159
165
  const origin = originOf(layer, backend);
160
166
  for (const row of rows) {
161
167
  // "Still running" is progress, not history: a test running now shows it as its current outcome.
@@ -248,13 +254,75 @@ export class TestHistory {
248
254
  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));
249
255
  return { run: runSummary(run, cells), results };
250
256
  }
257
+ /**
258
+ * Every recorded result that carries an attachment, newest first, with the values a query looks at. Built once per
259
+ * layer added. The domain is left to the caller (`artifactList`), which may know more specs than the layers recorded.
260
+ */
261
+ evidenceResults() {
262
+ if (this.#evidence?.rev === this.#rev)
263
+ return this.#evidence.items;
264
+ const items = [];
265
+ for (const [slot, list] of this.#bySlot) {
266
+ const i = this.#idents.get(slot);
267
+ for (const held of list) {
268
+ const e = held.e;
269
+ if (!e || e.attachments < 1)
270
+ continue;
271
+ const playable = (videoCounts(e.videos)?.recordings ?? 0) > 0;
272
+ const types = typesOf(e.roles, playable);
273
+ const artifactTags = [...(e.artifactTags ?? [])];
274
+ const o = held.o;
275
+ const result = {
276
+ key: i.slot, id: i.id, title: i.title, specPath: i.specPath, domain: null, at: o.at, status: held.s, environment: o.environment, producer: o.producer, kind: held.k,
277
+ parameters: o.parameters, branch: o.branch, sha: o.sha, run: o.run, runUrl: o.runUrl, layer: o.layer, backend: o.backend, attachments: e.attachments,
278
+ artifactTags, tags: [...e.tags], types, labels: e.labels, ...(e.videos ? { videos: e.videos } : {}),
279
+ };
280
+ const media = [
281
+ ...types.filter(type => type !== 'video').map(type => ({ type, tags: artifactTags })),
282
+ ...(e.videos ?? []).filter(video => video.available).map(video => ({ type: 'video', tags: video.tags ?? [] })),
283
+ ];
284
+ const labels = labelList(e.labels);
285
+ items.push({
286
+ result,
287
+ fields: {
288
+ text: [i.title, i.id, i.specPath ?? '', o.branch ?? '', o.producer, o.environment ?? '', ...artifactTags, ...e.tags, ...labels].join('\n').toLowerCase(),
289
+ artifactTags, tags: [...new Set([...e.tags, ...artifactTags])], types, media, labels,
290
+ environments: o.environment ? [o.environment] : [], producers: [o.producer], branches: o.branch ? [o.branch] : [], shas: o.sha ? [o.sha.toLowerCase()] : [],
291
+ outcomes: [held.s], at: o.at,
292
+ },
293
+ });
294
+ }
295
+ }
296
+ items.sort((a, b) => b.result.at - a.result.at || compareText(a.result.layer, b.result.layer) || compareText(a.result.key, b.result.key) || compareText(a.result.kind, b.result.kind) || compareText(a.result.producer, b.result.producer));
297
+ this.#evidence = { rev: this.#rev, items };
298
+ return items;
299
+ }
251
300
  /** The keys of the tests one run recorded. */
252
301
  testsIn(key) {
253
302
  return new Set((this.#runCells().get(key) ?? []).map(cell => cell.i.slot));
254
303
  }
255
304
  }
305
+ function takeEvidence(into, evidence) {
306
+ for (const tag of evidence.tags)
307
+ into.tags.add(tag);
308
+ for (const tag of evidence.artifactTags ?? [])
309
+ into.artifactTags.add(tag);
310
+ for (const role of evidence.roles)
311
+ into.roles.add(role);
312
+ for (const label of labelList(evidence.labels))
313
+ into.labels.add(label);
314
+ }
315
+ /** The media types of an evidence summary: its roles, with `video` only when a recording can be played. */
316
+ function typesOf(roles, playable) {
317
+ const types = new Set(roles);
318
+ types.delete('video');
319
+ if (playable)
320
+ types.add('video');
321
+ return [...types].sort();
322
+ }
256
323
  function runSummary(run, cells) {
257
324
  const counts = emptyCounts();
325
+ const carried = { tags: new Set(run.evidence.tags), artifactTags: new Set(run.evidence.artifactTags), roles: new Set(run.evidence.roles), labels: new Set(run.evidence.labels) };
258
326
  const tests = new Set();
259
327
  let total = 0, measured = false;
260
328
  const failed = [];
@@ -262,6 +330,8 @@ function runSummary(run, cells) {
262
330
  for (const cell of cells) {
263
331
  if (cell.r.e?.videos)
264
332
  videos.push(...cell.r.e.videos);
333
+ if (cell.r.e)
334
+ takeEvidence(carried, cell.r.e);
265
335
  counts[cell.r.s] += 1;
266
336
  tests.add(cell.i.slot);
267
337
  if (cell.r.d !== null) {
@@ -281,6 +351,10 @@ function runSummary(run, cells) {
281
351
  producers: [...run.producers].sort(), environments: [...run.environments].sort(),
282
352
  layers: run.layers, tests: tests.size, counts, totalDurationMs: measured ? total : null, failing,
283
353
  ...(video ? { videos: video } : {}),
354
+ ...(carried.artifactTags.size ? { artifactTags: [...carried.artifactTags].sort() } : {}),
355
+ ...(carried.roles.size ? { types: typesOf(carried.roles, (video?.recordings ?? 0) > 0) } : {}),
356
+ ...(carried.labels.size ? { labels: [...carried.labels].sort() } : {}),
357
+ ...(carried.tags.size ? { tags: [...carried.tags].sort() } : {}),
284
358
  };
285
359
  }
286
360
  function compareText(a, b) {
@@ -592,8 +666,59 @@ export function hasVideo(run) {
592
666
  export function runList(history, version, limit = 100, query = {}) {
593
667
  const all = history.runs();
594
668
  const withVideo = all.filter(hasVideo).length;
595
- const runs = query.video ? all.filter(hasVideo) : all;
596
- return { schemaVersion: TEST_CATALOG_VERSION, version, total: runs.length, runs: runs.slice(0, Math.max(0, limit)), withVideo };
669
+ const filter = forRuns(query);
670
+ const fields = all.map(fieldsOfRun);
671
+ const runs = all.filter((_, at) => matchesFilter(fields[at], filter));
672
+ return {
673
+ schemaVersion: TEST_CATALOG_VERSION, version, total: runs.length, runs: runs.slice(0, Math.max(0, limit)), withVideo,
674
+ filters: usedFilters(filter), facets: facetsOf(fields, filter),
675
+ };
676
+ }
677
+ /** What a query looks at in a run: see {@link ArtifactFilter}. A run's outcome is `failed` when any result failed. */
678
+ export function fieldsOfRun(run) {
679
+ const artifactTags = run.artifactTags ?? [];
680
+ // A run summary of an older server has no `types`, but it says whether it has a playable video.
681
+ const types = run.types ?? (hasVideo(run) ? ['video'] : []);
682
+ const labels = run.labels ?? [];
683
+ return {
684
+ text: [run.title ?? '', run.key, run.branch ?? '', run.sha ?? '', ...run.producers, ...run.environments, ...artifactTags, ...(run.tags ?? []), ...labels].join('\n').toLowerCase(),
685
+ artifactTags, tags: [...new Set([...(run.tags ?? []), ...artifactTags])], types, media: types.map(type => ({ type, tags: artifactTags })), labels,
686
+ environments: run.environments, producers: run.producers, branches: run.branch ? [run.branch] : [], shas: run.sha ? [run.sha.toLowerCase()] : [],
687
+ outcomes: [outcomeOfCounts(run.counts)], at: run.endedAtMs,
688
+ };
689
+ }
690
+ /**
691
+ * The recorded results that carry evidence and match a query, newest first, across the whole history (`limit` default
692
+ * 100). `total` and the facet counts apply to every match before `offset` and `limit`. `domainOf` tells the domain of a
693
+ * spec path when the caller knows more than the layers recorded (a server with a spec set); otherwise it is the
694
+ * recorded spec's domain, else the folder the path implies.
695
+ *
696
+ * @example
697
+ * ```ts
698
+ * const list = artifactList(history, 'v1', { artifactTag: ['demo'], type: ['video'], environment: ['ios'] });
699
+ * list.results.map(r => [r.title, r.run, r.sha, r.status]);
700
+ * ```
701
+ */
702
+ export function artifactList(history, version, query = {}, page = {}) {
703
+ const domains = new Map();
704
+ const domainOf = (path) => {
705
+ if (path === null)
706
+ return null;
707
+ if (!domains.has(path))
708
+ domains.set(path, (page.domainOf?.(path) ?? catalogSpec(path, known => history.specs.get(known))?.domain) || null);
709
+ return domains.get(path);
710
+ };
711
+ const items = history.evidenceResults().map(({ result, fields }) => {
712
+ const domain = domainOf(result.specPath);
713
+ return { result: { ...result, domain }, fields: { ...fields, domains: domain ? [domain] : [], specs: result.specPath ? [normalSpecPath(result.specPath)] : [] } };
714
+ });
715
+ const matched = items.filter(item => matchesFilter(item.fields, query));
716
+ const limit = Math.max(0, page.limit ?? 100);
717
+ const offset = Math.max(0, page.offset ?? 0);
718
+ return {
719
+ schemaVersion: TEST_CATALOG_VERSION, version, total: matched.length, offset, limit,
720
+ results: matched.slice(offset, offset + limit).map(item => item.result), filters: usedFilters(query), facets: facetsOf(items.map(item => item.fields), query),
721
+ };
597
722
  }
598
723
  /** One run with every result it recorded, or null when no run has that key. */
599
724
  export function runDetail(history, version, key) {
@@ -602,7 +727,7 @@ export function runDetail(history, version, key) {
602
727
  }
603
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`. */
604
729
  export function parseTestQuery(params) {
605
- const many = (name) => params.getAll(name).flatMap(value => value.split(',')).map(value => value.trim()).filter(Boolean);
730
+ const many = (name) => params.getAll(name).flatMap(splitList);
606
731
  const number = (name) => { const value = params.get(name); return value !== null && /^\d+$/.test(value) ? Number(value) : undefined; };
607
732
  const sort = params.get('sort');
608
733
  const query = {};
@@ -726,6 +851,16 @@ export function runsMarkdown(list) {
726
851
  ...list.runs.map(r => `| ${iso(r.endedAtMs)} | ${cell(r.branch ?? '—')} | ${r.sha?.slice(0, 10) ?? '—'}${freshnessNote(r.freshness)} | ${r.environments.join(', ') || '—'} | ${r.tests} | ${r.counts.passed} | ${r.counts.failed} | ${r.counts['not-run']} | ${formatMs(r.spanMs)} | ${videosText(r.videos)} | ${cell(r.title ? `${r.title} (${r.key})` : r.key)} |`),
727
852
  ].join('\n')}\n`;
728
853
  }
854
+ /** The artifact results as Markdown, for an agent or a terminal. */
855
+ export function artifactsMarkdown(list) {
856
+ return `${[
857
+ `# Artifacts (${list.results.length} of ${list.total})`,
858
+ '',
859
+ '| Recorded | Test | Status | Environment | Branch | Commit | Types | Tags | Videos | Run |',
860
+ '|---|---|---|---|---|---|---|---|---|---|',
861
+ ...list.results.map(r => `| ${iso(r.at)} | ${cell(r.title)} | ${r.status} | ${r.environment ?? '—'} | ${cell(r.branch ?? '—')} | ${r.sha?.slice(0, 10) ?? '—'} | ${r.types.join(', ') || '—'} | ${r.artifactTags.map(displayTag).join(' ') || '—'} | ${videosText(videoCounts(r.videos))} | ${cell(r.runUrl ?? r.run)} |`),
862
+ ].join('\n')}\n`;
863
+ }
729
864
  /** One run as Markdown. */
730
865
  export function runMarkdown(detail) {
731
866
  const { run } = detail;
@@ -86,6 +86,7 @@ export declare const VideoRefSchema: z.ZodObject<{
86
86
  owner: z.ZodString;
87
87
  attempt: z.ZodOptional<z.ZodNumber>;
88
88
  step: z.ZodOptional<z.ZodString>;
89
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
89
90
  recording: z.ZodOptional<z.ZodString>;
90
91
  available: z.ZodBoolean;
91
92
  }, z.core.$loose>;
@@ -111,6 +112,7 @@ export declare const EvidenceSummarySchema: z.ZodObject<{
111
112
  owner: z.ZodString;
112
113
  attempt: z.ZodOptional<z.ZodNumber>;
113
114
  step: z.ZodOptional<z.ZodString>;
115
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
114
116
  recording: z.ZodOptional<z.ZodString>;
115
117
  available: z.ZodBoolean;
116
118
  }, z.core.$loose>>>;
@@ -161,6 +163,7 @@ export declare const LayerHeaderSchema: z.ZodObject<{
161
163
  owner: z.ZodString;
162
164
  attempt: z.ZodOptional<z.ZodNumber>;
163
165
  step: z.ZodOptional<z.ZodString>;
166
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
164
167
  recording: z.ZodOptional<z.ZodString>;
165
168
  available: z.ZodBoolean;
166
169
  }, z.core.$loose>>>;
@@ -211,6 +214,7 @@ export declare const LayerRowSchema: z.ZodObject<{
211
214
  owner: z.ZodString;
212
215
  attempt: z.ZodOptional<z.ZodNumber>;
213
216
  step: z.ZodOptional<z.ZodString>;
217
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
214
218
  recording: z.ZodOptional<z.ZodString>;
215
219
  available: z.ZodBoolean;
216
220
  }, z.core.$loose>>>;
@@ -259,6 +263,7 @@ export declare const LayerRowsSchema: z.ZodObject<{
259
263
  owner: z.ZodString;
260
264
  attempt: z.ZodOptional<z.ZodNumber>;
261
265
  step: z.ZodOptional<z.ZodString>;
266
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
262
267
  recording: z.ZodOptional<z.ZodString>;
263
268
  available: z.ZodBoolean;
264
269
  }, z.core.$loose>>>;
@@ -309,6 +314,7 @@ export declare const LayerEvidenceSchema: z.ZodObject<{
309
314
  owner: z.ZodString;
310
315
  attempt: z.ZodOptional<z.ZodNumber>;
311
316
  step: z.ZodOptional<z.ZodString>;
317
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
312
318
  recording: z.ZodOptional<z.ZodString>;
313
319
  available: z.ZodBoolean;
314
320
  }, z.core.$loose>>>;
@@ -364,6 +370,7 @@ export declare const LayerEvidenceSchema: z.ZodObject<{
364
370
  owner: z.ZodString;
365
371
  attempt: z.ZodOptional<z.ZodNumber>;
366
372
  step: z.ZodOptional<z.ZodString>;
373
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
367
374
  recording: z.ZodOptional<z.ZodString>;
368
375
  available: z.ZodBoolean;
369
376
  }, z.core.$loose>>>;
@@ -423,6 +430,7 @@ export declare const EffectiveEvidenceSchema: z.ZodObject<{
423
430
  owner: z.ZodString;
424
431
  attempt: z.ZodOptional<z.ZodNumber>;
425
432
  step: z.ZodOptional<z.ZodString>;
433
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
426
434
  recording: z.ZodOptional<z.ZodString>;
427
435
  available: z.ZodBoolean;
428
436
  }, z.core.$loose>>>;
@@ -478,6 +486,7 @@ export declare const EffectiveEvidenceSchema: z.ZodObject<{
478
486
  owner: z.ZodString;
479
487
  attempt: z.ZodOptional<z.ZodNumber>;
480
488
  step: z.ZodOptional<z.ZodString>;
489
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
481
490
  recording: z.ZodOptional<z.ZodString>;
482
491
  available: z.ZodBoolean;
483
492
  }, z.core.$loose>>>;
@@ -558,6 +567,7 @@ export declare const EffectiveDeltaSchema: z.ZodObject<{
558
567
  owner: z.ZodString;
559
568
  attempt: z.ZodOptional<z.ZodNumber>;
560
569
  step: z.ZodOptional<z.ZodString>;
570
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
561
571
  recording: z.ZodOptional<z.ZodString>;
562
572
  available: z.ZodBoolean;
563
573
  }, z.core.$loose>>>;
@@ -613,6 +623,7 @@ export declare const EffectiveDeltaSchema: z.ZodObject<{
613
623
  owner: z.ZodString;
614
624
  attempt: z.ZodOptional<z.ZodNumber>;
615
625
  step: z.ZodOptional<z.ZodString>;
626
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
616
627
  recording: z.ZodOptional<z.ZodString>;
617
628
  available: z.ZodBoolean;
618
629
  }, z.core.$loose>>>;
@@ -696,6 +707,7 @@ export declare const EvidencePageSchema: z.ZodObject<{
696
707
  owner: z.ZodString;
697
708
  attempt: z.ZodOptional<z.ZodNumber>;
698
709
  step: z.ZodOptional<z.ZodString>;
710
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
699
711
  recording: z.ZodOptional<z.ZodString>;
700
712
  available: z.ZodBoolean;
701
713
  }, z.core.$loose>>>;
@@ -1096,6 +1108,7 @@ export declare const TestResultEntrySchema: z.ZodObject<{
1096
1108
  owner: z.ZodString;
1097
1109
  attempt: z.ZodOptional<z.ZodNumber>;
1098
1110
  step: z.ZodOptional<z.ZodString>;
1111
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
1099
1112
  recording: z.ZodOptional<z.ZodString>;
1100
1113
  available: z.ZodBoolean;
1101
1114
  }, z.core.$loose>>>;
@@ -1156,6 +1169,7 @@ export declare const TestSummarySchema: z.ZodObject<{
1156
1169
  owner: z.ZodString;
1157
1170
  attempt: z.ZodOptional<z.ZodNumber>;
1158
1171
  step: z.ZodOptional<z.ZodString>;
1172
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
1159
1173
  recording: z.ZodOptional<z.ZodString>;
1160
1174
  available: z.ZodBoolean;
1161
1175
  }, z.core.$loose>>>;
@@ -1406,6 +1420,7 @@ export declare const TestCatalogSchema: z.ZodObject<{
1406
1420
  owner: z.ZodString;
1407
1421
  attempt: z.ZodOptional<z.ZodNumber>;
1408
1422
  step: z.ZodOptional<z.ZodString>;
1423
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
1409
1424
  recording: z.ZodOptional<z.ZodString>;
1410
1425
  available: z.ZodBoolean;
1411
1426
  }, z.core.$loose>>>;
@@ -1587,6 +1602,7 @@ export declare const TestHistorySchema: z.ZodObject<{
1587
1602
  owner: z.ZodString;
1588
1603
  attempt: z.ZodOptional<z.ZodNumber>;
1589
1604
  step: z.ZodOptional<z.ZodString>;
1605
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
1590
1606
  recording: z.ZodOptional<z.ZodString>;
1591
1607
  available: z.ZodBoolean;
1592
1608
  }, z.core.$loose>>>;
@@ -1665,6 +1681,7 @@ export declare const TestHistorySchema: z.ZodObject<{
1665
1681
  owner: z.ZodString;
1666
1682
  attempt: z.ZodOptional<z.ZodNumber>;
1667
1683
  step: z.ZodOptional<z.ZodString>;
1684
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
1668
1685
  recording: z.ZodOptional<z.ZodString>;
1669
1686
  available: z.ZodBoolean;
1670
1687
  }, z.core.$loose>>>;
@@ -1708,6 +1725,50 @@ export declare const ReportRunSchema: z.ZodObject<{
1708
1725
  recordings: z.ZodNumber;
1709
1726
  unavailable: z.ZodNumber;
1710
1727
  }, z.core.$loose>>;
1728
+ artifactTags: z.ZodOptional<z.ZodArray<z.ZodString>>;
1729
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
1730
+ types: z.ZodOptional<z.ZodArray<z.ZodString>>;
1731
+ labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
1732
+ }, z.core.$loose>;
1733
+ /** A value of a filter dimension and how many records have it. */
1734
+ export declare const QueryFacetValueSchema: z.ZodObject<{
1735
+ value: z.ZodString;
1736
+ count: z.ZodNumber;
1737
+ }, z.core.$loose>;
1738
+ /** The facets of a runs or artifacts query: per dimension, its values with disjunctive counts (the list's count if that value were the only one chosen, every other dimension's filter applied). */
1739
+ export declare const QueryFacetsSchema: z.ZodObject<{
1740
+ artifactTags: z.ZodArray<z.ZodObject<{
1741
+ value: z.ZodString;
1742
+ count: z.ZodNumber;
1743
+ }, z.core.$loose>>;
1744
+ types: z.ZodArray<z.ZodObject<{
1745
+ value: z.ZodString;
1746
+ count: z.ZodNumber;
1747
+ }, z.core.$loose>>;
1748
+ labels: z.ZodArray<z.ZodObject<{
1749
+ value: z.ZodString;
1750
+ count: z.ZodNumber;
1751
+ }, z.core.$loose>>;
1752
+ environments: z.ZodArray<z.ZodObject<{
1753
+ value: z.ZodString;
1754
+ count: z.ZodNumber;
1755
+ }, z.core.$loose>>;
1756
+ producers: z.ZodArray<z.ZodObject<{
1757
+ value: z.ZodString;
1758
+ count: z.ZodNumber;
1759
+ }, z.core.$loose>>;
1760
+ branches: z.ZodArray<z.ZodObject<{
1761
+ value: z.ZodString;
1762
+ count: z.ZodNumber;
1763
+ }, z.core.$loose>>;
1764
+ outcomes: z.ZodArray<z.ZodObject<{
1765
+ value: z.ZodString;
1766
+ count: z.ZodNumber;
1767
+ }, z.core.$loose>>;
1768
+ domains: z.ZodOptional<z.ZodArray<z.ZodObject<{
1769
+ value: z.ZodString;
1770
+ count: z.ZodNumber;
1771
+ }, z.core.$loose>>>;
1711
1772
  }, z.core.$loose>;
1712
1773
  /** `GET /api/reports/runs`: runs, newest first. */
1713
1774
  export declare const RunListSchema: z.ZodObject<{
@@ -1750,8 +1811,167 @@ export declare const RunListSchema: z.ZodObject<{
1750
1811
  recordings: z.ZodNumber;
1751
1812
  unavailable: z.ZodNumber;
1752
1813
  }, z.core.$loose>>;
1814
+ artifactTags: z.ZodOptional<z.ZodArray<z.ZodString>>;
1815
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
1816
+ types: z.ZodOptional<z.ZodArray<z.ZodString>>;
1817
+ labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
1753
1818
  }, z.core.$loose>>;
1754
1819
  withVideo: z.ZodOptional<z.ZodNumber>;
1820
+ filters: z.ZodOptional<z.ZodArray<z.ZodString>>;
1821
+ facets: z.ZodOptional<z.ZodObject<{
1822
+ artifactTags: z.ZodArray<z.ZodObject<{
1823
+ value: z.ZodString;
1824
+ count: z.ZodNumber;
1825
+ }, z.core.$loose>>;
1826
+ types: z.ZodArray<z.ZodObject<{
1827
+ value: z.ZodString;
1828
+ count: z.ZodNumber;
1829
+ }, z.core.$loose>>;
1830
+ labels: z.ZodArray<z.ZodObject<{
1831
+ value: z.ZodString;
1832
+ count: z.ZodNumber;
1833
+ }, z.core.$loose>>;
1834
+ environments: z.ZodArray<z.ZodObject<{
1835
+ value: z.ZodString;
1836
+ count: z.ZodNumber;
1837
+ }, z.core.$loose>>;
1838
+ producers: z.ZodArray<z.ZodObject<{
1839
+ value: z.ZodString;
1840
+ count: z.ZodNumber;
1841
+ }, z.core.$loose>>;
1842
+ branches: z.ZodArray<z.ZodObject<{
1843
+ value: z.ZodString;
1844
+ count: z.ZodNumber;
1845
+ }, z.core.$loose>>;
1846
+ outcomes: z.ZodArray<z.ZodObject<{
1847
+ value: z.ZodString;
1848
+ count: z.ZodNumber;
1849
+ }, z.core.$loose>>;
1850
+ domains: z.ZodOptional<z.ZodArray<z.ZodObject<{
1851
+ value: z.ZodString;
1852
+ count: z.ZodNumber;
1853
+ }, z.core.$loose>>>;
1854
+ }, z.core.$loose>>;
1855
+ }, z.core.$loose>;
1856
+ /** One recorded result that carries evidence: where and when it was recorded and what it carries. */
1857
+ export declare const ArtifactResultSchema: z.ZodObject<{
1858
+ key: z.ZodString;
1859
+ id: z.ZodString;
1860
+ title: z.ZodString;
1861
+ specPath: z.ZodNullable<z.ZodString>;
1862
+ domain: z.ZodNullable<z.ZodString>;
1863
+ at: z.ZodNumber;
1864
+ status: z.ZodEnum<{
1865
+ passed: "passed";
1866
+ failed: "failed";
1867
+ running: "running";
1868
+ "not-run": "not-run";
1869
+ }>;
1870
+ environment: z.ZodNullable<z.ZodString>;
1871
+ producer: z.ZodString;
1872
+ kind: z.ZodString;
1873
+ parameters: z.ZodRecord<z.ZodString, z.ZodString>;
1874
+ branch: z.ZodNullable<z.ZodString>;
1875
+ sha: z.ZodNullable<z.ZodString>;
1876
+ run: z.ZodString;
1877
+ runUrl: z.ZodNullable<z.ZodString>;
1878
+ layer: z.ZodString;
1879
+ backend: z.ZodString;
1880
+ attachments: z.ZodNumber;
1881
+ artifactTags: z.ZodArray<z.ZodString>;
1882
+ tags: z.ZodArray<z.ZodString>;
1883
+ types: z.ZodArray<z.ZodString>;
1884
+ labels: z.ZodRecord<z.ZodString, z.ZodString>;
1885
+ videos: z.ZodOptional<z.ZodArray<z.ZodObject<{
1886
+ name: z.ZodString;
1887
+ owner: z.ZodString;
1888
+ attempt: z.ZodOptional<z.ZodNumber>;
1889
+ step: z.ZodOptional<z.ZodString>;
1890
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
1891
+ recording: z.ZodOptional<z.ZodString>;
1892
+ available: z.ZodBoolean;
1893
+ }, z.core.$loose>>>;
1894
+ }, z.core.$loose>;
1895
+ /** `GET /api/reports/artifacts`: recorded results that carry evidence, newest first, with the facets of the query. */
1896
+ export declare const ArtifactListSchema: z.ZodObject<{
1897
+ schemaVersion: z.ZodLiteral<1>;
1898
+ version: z.ZodString;
1899
+ total: z.ZodNumber;
1900
+ offset: z.ZodNumber;
1901
+ limit: z.ZodNumber;
1902
+ results: z.ZodArray<z.ZodObject<{
1903
+ key: z.ZodString;
1904
+ id: z.ZodString;
1905
+ title: z.ZodString;
1906
+ specPath: z.ZodNullable<z.ZodString>;
1907
+ domain: z.ZodNullable<z.ZodString>;
1908
+ at: z.ZodNumber;
1909
+ status: z.ZodEnum<{
1910
+ passed: "passed";
1911
+ failed: "failed";
1912
+ running: "running";
1913
+ "not-run": "not-run";
1914
+ }>;
1915
+ environment: z.ZodNullable<z.ZodString>;
1916
+ producer: z.ZodString;
1917
+ kind: z.ZodString;
1918
+ parameters: z.ZodRecord<z.ZodString, z.ZodString>;
1919
+ branch: z.ZodNullable<z.ZodString>;
1920
+ sha: z.ZodNullable<z.ZodString>;
1921
+ run: z.ZodString;
1922
+ runUrl: z.ZodNullable<z.ZodString>;
1923
+ layer: z.ZodString;
1924
+ backend: z.ZodString;
1925
+ attachments: z.ZodNumber;
1926
+ artifactTags: z.ZodArray<z.ZodString>;
1927
+ tags: z.ZodArray<z.ZodString>;
1928
+ types: z.ZodArray<z.ZodString>;
1929
+ labels: z.ZodRecord<z.ZodString, z.ZodString>;
1930
+ videos: z.ZodOptional<z.ZodArray<z.ZodObject<{
1931
+ name: z.ZodString;
1932
+ owner: z.ZodString;
1933
+ attempt: z.ZodOptional<z.ZodNumber>;
1934
+ step: z.ZodOptional<z.ZodString>;
1935
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
1936
+ recording: z.ZodOptional<z.ZodString>;
1937
+ available: z.ZodBoolean;
1938
+ }, z.core.$loose>>>;
1939
+ }, z.core.$loose>>;
1940
+ filters: z.ZodArray<z.ZodString>;
1941
+ facets: z.ZodObject<{
1942
+ artifactTags: z.ZodArray<z.ZodObject<{
1943
+ value: z.ZodString;
1944
+ count: z.ZodNumber;
1945
+ }, z.core.$loose>>;
1946
+ types: z.ZodArray<z.ZodObject<{
1947
+ value: z.ZodString;
1948
+ count: z.ZodNumber;
1949
+ }, z.core.$loose>>;
1950
+ labels: z.ZodArray<z.ZodObject<{
1951
+ value: z.ZodString;
1952
+ count: z.ZodNumber;
1953
+ }, z.core.$loose>>;
1954
+ environments: z.ZodArray<z.ZodObject<{
1955
+ value: z.ZodString;
1956
+ count: z.ZodNumber;
1957
+ }, z.core.$loose>>;
1958
+ producers: z.ZodArray<z.ZodObject<{
1959
+ value: z.ZodString;
1960
+ count: z.ZodNumber;
1961
+ }, z.core.$loose>>;
1962
+ branches: z.ZodArray<z.ZodObject<{
1963
+ value: z.ZodString;
1964
+ count: z.ZodNumber;
1965
+ }, z.core.$loose>>;
1966
+ outcomes: z.ZodArray<z.ZodObject<{
1967
+ value: z.ZodString;
1968
+ count: z.ZodNumber;
1969
+ }, z.core.$loose>>;
1970
+ domains: z.ZodOptional<z.ZodArray<z.ZodObject<{
1971
+ value: z.ZodString;
1972
+ count: z.ZodNumber;
1973
+ }, z.core.$loose>>>;
1974
+ }, z.core.$loose>;
1755
1975
  }, z.core.$loose>;
1756
1976
  /** `GET /api/reports/runs/detail`: one run and its last result for every test, environment and producer it recorded. */
1757
1977
  export declare const RunDetailSchema: z.ZodObject<{
@@ -1793,6 +2013,10 @@ export declare const RunDetailSchema: z.ZodObject<{
1793
2013
  recordings: z.ZodNumber;
1794
2014
  unavailable: z.ZodNumber;
1795
2015
  }, z.core.$loose>>;
2016
+ artifactTags: z.ZodOptional<z.ZodArray<z.ZodString>>;
2017
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
2018
+ types: z.ZodOptional<z.ZodArray<z.ZodString>>;
2019
+ labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
1796
2020
  }, z.core.$loose>;
1797
2021
  results: z.ZodArray<z.ZodObject<{
1798
2022
  key: z.ZodString;
@@ -1816,6 +2040,7 @@ export declare const RunDetailSchema: z.ZodObject<{
1816
2040
  owner: z.ZodString;
1817
2041
  attempt: z.ZodOptional<z.ZodNumber>;
1818
2042
  step: z.ZodOptional<z.ZodString>;
2043
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
1819
2044
  recording: z.ZodOptional<z.ZodString>;
1820
2045
  available: z.ZodBoolean;
1821
2046
  }, z.core.$loose>>>;
@@ -68,6 +68,7 @@ export const VideoRefSchema = z.looseObject({
68
68
  owner: z.string().describe('Who holds it: `layer`, `check`, `attempt` or `step`. An open vocabulary: treat an unknown owner as a check'),
69
69
  attempt: z.number().int().nonnegative().optional().describe('The retry attempt that holds it, or that holds the step that does'),
70
70
  step: z.string().optional().describe('The name of the step that holds it'),
71
+ tags: z.array(z.string()).optional().describe('The normalized tags of this attachment itself. Absent when it has none (and in documents of older servers)'),
71
72
  recording: z.string().optional().describe('What identifies the bytes; references with the same value are one recording. Absent when it cannot be played'),
72
73
  available: z.boolean().describe('False when the reference names no bytes (no bundle key, address or inline text)')
73
74
  });
@@ -396,7 +397,19 @@ export const ReportRunSchema = z.looseObject({
396
397
  layers: z.number().int().nonnegative(), tests: z.number().int().nonnegative(), counts: StatusCountsSchema,
397
398
  totalDurationMs: z.number().nullable(),
398
399
  failing: z.array(z.looseObject({ key: z.string(), title: z.string(), environment: z.string().nullable(), detail: z.string() })),
399
- videos: VideoCountsSchema.optional().describe('Its videos, from its layers and the last result of each test. Absent when it has none or the report carries no video information')
400
+ videos: VideoCountsSchema.optional().describe('Its videos, from its layers and the last result of each test. Absent when it has none or the report carries no video information'),
401
+ artifactTags: z.array(z.string()).optional().describe('The normalized tags of the attachments of its results, sorted and distinct. Absent when none is tagged, and in reports of older servers'),
402
+ tags: z.array(z.string()).optional().describe('The tags of its checks and layers, which its attachments inherit (what `inherited=1` also matches). Absent when none'),
403
+ types: z.array(z.string()).optional().describe('The media types (roles) of its attachments, sorted and distinct; `video` only for a playable recording. Absent when it has no attachment'),
404
+ labels: z.array(z.string()).optional().describe('The `key=value` labels of its results and layers, sorted and distinct. Absent when it has none')
405
+ });
406
+ /** A value of a filter dimension and how many records have it. */
407
+ export const QueryFacetValueSchema = z.looseObject({ value: z.string(), count: z.number().int().nonnegative() });
408
+ /** The facets of a runs or artifacts query: per dimension, its values with disjunctive counts (the list's count if that value were the only one chosen, every other dimension's filter applied). */
409
+ export const QueryFacetsSchema = z.looseObject({
410
+ artifactTags: z.array(QueryFacetValueSchema), types: z.array(QueryFacetValueSchema), labels: z.array(QueryFacetValueSchema).describe('Values are `key=value`'),
411
+ environments: z.array(QueryFacetValueSchema), producers: z.array(QueryFacetValueSchema), branches: z.array(QueryFacetValueSchema), outcomes: z.array(QueryFacetValueSchema),
412
+ domains: z.array(QueryFacetValueSchema).optional().describe('Only for artifacts: runs have no domain')
400
413
  });
401
414
  /** `GET /api/reports/runs`: runs, newest first. */
402
415
  export const RunListSchema = z.looseObject({
@@ -404,7 +417,33 @@ export const RunListSchema = z.looseObject({
404
417
  version: z.string(),
405
418
  total: z.number().int().nonnegative().describe('Runs that matched, before `limit`'),
406
419
  runs: z.array(ReportRunSchema),
407
- withVideo: z.number().int().nonnegative().optional().describe('How many of all runs (before `video` and `limit`) have a playable video recording')
420
+ withVideo: z.number().int().nonnegative().optional().describe('How many of all runs (before `video` and `limit`) have a playable video recording'),
421
+ filters: z.array(z.string()).optional().describe('The filter parameters this server applied (`artifactTag`, `type`, `outcome`…). A viewer that asked for one that is missing here knows the server ignored it. Absent on servers that predate filtering'),
422
+ facets: QueryFacetsSchema.optional().describe('The values each filter dimension offers, counted over every run. Absent on servers that predate filtering')
423
+ });
424
+ /** One recorded result that carries evidence: where and when it was recorded and what it carries. */
425
+ export const ArtifactResultSchema = z.looseObject({
426
+ key: z.string().describe('The test\'s key, as in `/api/reports/tests`'), id: z.string(), title: z.string(), specPath: z.string().nullable(), domain: z.string().nullable(),
427
+ at: z.number().describe('When it was recorded, epoch milliseconds'), status: EvidenceStatusSchema,
428
+ environment: z.string().nullable(), producer: z.string(), kind: z.string(), parameters: z.record(z.string(), z.string()),
429
+ branch: z.string().nullable(), sha: z.string().nullable(), run: z.string().describe('The run\'s key, as in `/api/reports/runs`'), runUrl: z.string().nullable(),
430
+ layer: z.string().describe('The id of the layer that recorded it. Pass it as the `layer` of the explorer to see exactly this result'), backend: z.string(),
431
+ attachments: z.number().int().nonnegative(),
432
+ artifactTags: z.array(z.string()).describe('Normalized tags of its attachments'),
433
+ tags: z.array(z.string()).describe('Tags of its check and run, which its attachments inherit'),
434
+ types: z.array(z.string()).describe('Media types (roles) of its attachments; `video` only for a playable recording'),
435
+ labels: z.record(z.string(), z.string()),
436
+ videos: z.array(VideoRefSchema).optional()
437
+ });
438
+ /** `GET /api/reports/artifacts`: recorded results that carry evidence, newest first, with the facets of the query. */
439
+ export const ArtifactListSchema = z.looseObject({
440
+ schemaVersion: z.literal(1),
441
+ version: z.string(),
442
+ total: z.number().int().nonnegative().describe('Results that matched, before `offset` and `limit`'),
443
+ offset: z.number().int().nonnegative(), limit: z.number().int().nonnegative(),
444
+ results: z.array(ArtifactResultSchema),
445
+ filters: z.array(z.string()).describe('The filters of the query that were applied'),
446
+ facets: QueryFacetsSchema
408
447
  });
409
448
  /** `GET /api/reports/runs/detail`: one run and its last result for every test, environment and producer it recorded. */
410
449
  export const RunDetailSchema = z.looseObject({