@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
@@ -27,7 +27,8 @@ import { ReportIndex } from '../../reports/src/report-index.js';
27
27
  import { listRunSummaries, readRunFile } from '../../reports/src/run-bundle.js';
28
28
  import { latestSpecSet, readSpecBody } from '../../reports/src/spec-set.js';
29
29
  import { catalogHealth, healthMarkdown, parseRootMappings } from '../../reports/src/catalog-health.js';
30
- import { catalogMarkdown, catalogModel, historyMarkdown, parseTestQuery, runDetail, runList, runMarkdown, runsMarkdown, testCatalog, testHistory } from '../../reports/src/test-catalog.js';
30
+ import { parseArtifactFilter } from '../../reports/src/artifact-query.js';
31
+ import { artifactList, artifactsMarkdown, catalogMarkdown, catalogModel, historyMarkdown, parseTestQuery, runDetail, runList, runMarkdown, runsMarkdown, testCatalog, testHistory } from '../../reports/src/test-catalog.js';
31
32
  import { llmsTxt } from './llms.js';
32
33
  import { openApiDocument } from './openapi.js';
33
34
  import { asProblem, problemResponse } from './problem.js';
@@ -256,7 +257,7 @@ function createProjectApplication(options) {
256
257
  name: 'atlas', version: options.version ?? null, role: identity?.role ?? 'read', open: auth.open,
257
258
  write: options.write, ready: index.ready, viewers: events.count, time: now(),
258
259
  backends: snapshot.backends.map(({ id, label, available }) => ({ id, label, available, write: id === options.write })),
259
- ...(({ backends: _unused, ...rest }) => rest)(index.stats()), capabilities: { events: true, ingest: true, store: true, checks: true, status: true, effective: true, live: true, tests: true, artifactTags: true }
260
+ ...(({ backends: _unused, ...rest }) => rest)(index.stats()), capabilities: { events: true, ingest: true, store: true, checks: true, status: true, effective: true, live: true, tests: true, artifactTags: true, artifactSearch: true }
260
261
  });
261
262
  });
262
263
  app.get('/api/live', async (c) => c.json({ runs: await live.list() }, 200, { 'cache-control': 'no-store' }));
@@ -471,9 +472,19 @@ function createProjectApplication(options) {
471
472
  return notReady(c);
472
473
  const found = await catalog();
473
474
  const limit = /^\d+$/.test(c.req.query('limit') ?? '') ? Number(c.req.query('limit')) : 100;
474
- const video = ['1', 'true'].includes(c.req.query('video') ?? '');
475
- const document = () => runList(index.history, found.version, limit, { video });
476
- return answer(c, found.version, document, () => runsMarkdown(document()), `runs|${limit}|${video}|${wantsMarkdown(c)}`);
475
+ const search = new URL(c.req.url).searchParams;
476
+ const document = () => runList(index.history, found.version, limit, parseArtifactFilter(search));
477
+ return answer(c, found.version, document, () => runsMarkdown(document()), `runs|${search.toString()}|${wantsMarkdown(c)}`);
478
+ });
479
+ app.get('/api/reports/artifacts', async (c) => {
480
+ if (!index.ready)
481
+ return notReady(c);
482
+ const found = await catalog();
483
+ const search = new URL(c.req.url).searchParams;
484
+ const number = (name, fallback) => (/^\d+$/.test(search.get(name) ?? '') ? Number(search.get(name)) : fallback);
485
+ const domains = new Map(found.facets.specs.map(spec => [spec.path, spec.domain]));
486
+ const document = () => artifactList(index.history, found.version, parseArtifactFilter(search), { limit: number('limit', 100), offset: number('offset', 0), domainOf: path => domains.get(path) });
487
+ return answer(c, found.version, document, () => artifactsMarkdown(document()), `artifacts|${search.toString()}|${wantsMarkdown(c)}`);
477
488
  });
478
489
  app.get('/api/reports/runs/detail', async (c) => {
479
490
  if (!index.ready)
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { z } from 'zod/v4';
16
16
  import { ProjectsSchema } from '../../schema/src/project.js';
17
- import { EffectiveDeltaSchema, EffectiveEvidenceSchema, EvidencePageSchema, HealthSchema, LayerRowsSchema, CheckDetailSchema, OutlineSchema, ProblemSchema, ReadySchema, ServerInfoSchema, StoreListingSchema, StoreWriteResultSchema, ChecksResultSchema, HelloEventSchema, ReportEventSchema, PingEventSchema, TestCatalogSchema, CatalogHealthSchema, TestHistorySchema, RunListSchema, RunDetailSchema, ArtifactTagEditSchema, ArtifactTagEntrySchema, ArtifactTagsSchema } from '../../schema/src/api.js';
17
+ import { EffectiveDeltaSchema, EffectiveEvidenceSchema, EvidencePageSchema, HealthSchema, LayerRowsSchema, CheckDetailSchema, OutlineSchema, ProblemSchema, ReadySchema, ServerInfoSchema, StoreListingSchema, StoreWriteResultSchema, ChecksResultSchema, HelloEventSchema, ReportEventSchema, PingEventSchema, TestCatalogSchema, CatalogHealthSchema, TestHistorySchema, RunListSchema, RunDetailSchema, ArtifactListSchema, ArtifactTagEditSchema, ArtifactTagEntrySchema, ArtifactTagsSchema } from '../../schema/src/api.js';
18
18
  import { LiveRunSchema, LiveRunStartSchema, LiveRunUpdateSchema, ServerEditableSettingsSchema, ServerSettingsSnapshotSchema, GcPolicySchema, GcSnapshotSchema, GcRunResultSchema, AtlasChangeSchema, PublishChecksSchema, ReportSourcesSchema, ReportStatusSchema, RunsIndexSchema, SpecIndexSchema } from '../../schema/src/index.js';
19
19
  /** The version of the API described, `major.minor.patch`: the minor grows when an operation or field is added, the major never within `/api`. */
20
20
  export const API_VERSION = '1.5.0';
@@ -37,7 +37,7 @@ const COMPONENTS = {
37
37
  StoreListing: [StoreListingSchema, 'output'], StoreWriteResult: [StoreWriteResultSchema, 'output'],
38
38
  Outline: [OutlineSchema, 'output'], EvidencePage: [EvidencePageSchema, 'output'], LayerRows: [LayerRowsSchema, 'output'], CheckDetail: [CheckDetailSchema, 'output'],
39
39
  ArtifactTagEdit: [ArtifactTagEditSchema, 'input'], ArtifactTagEntry: [ArtifactTagEntrySchema, 'output'], ArtifactTags: [ArtifactTagsSchema, 'output'],
40
- TestCatalog: [TestCatalogSchema, 'output'], CatalogHealth: [CatalogHealthSchema, 'output'], TestHistory: [TestHistorySchema, 'output'], RunList: [RunListSchema, 'output'], RunDetail: [RunDetailSchema, 'output'],
40
+ TestCatalog: [TestCatalogSchema, 'output'], CatalogHealth: [CatalogHealthSchema, 'output'], TestHistory: [TestHistorySchema, 'output'], RunList: [RunListSchema, 'output'], ArtifactList: [ArtifactListSchema, 'output'], RunDetail: [RunDetailSchema, 'output'],
41
41
  SpecIndex: [SpecIndexSchema, 'output'], ReportSources: [ReportSourcesSchema, 'output'], RunsIndex: [RunsIndexSchema, 'output'],
42
42
  RunEvent: [LiveRunSchema, 'output'], HelloEvent: [HelloEventSchema, 'output'], ReportEvent: [ReportEventSchema, 'output'], AtlasChangeEvent: [AtlasChangeSchema, 'output'], PingEvent: [PingEventSchema, 'output']
43
43
  };
@@ -343,14 +343,52 @@ const BASE_OPERATIONS = [
343
343
  {
344
344
  route: 'GET /api/reports/runs', path: '/api/reports/runs', operationId: 'getReportRuns', tag: 'Report', role: 'read',
345
345
  summary: 'The runs that recorded the report',
346
- description: 'Every run, newest first: layers grouped by their CI run URL (else branch and commit), with their environments, producers, how many tests they recorded, their results by status, the time they spanned, up to twenty failures and their video counts (references, distinct playable recordings, unavailable). Send `format=markdown` for Markdown.',
346
+ description: 'Every run, newest first: layers grouped by their CI run URL (else branch and commit), with their environments, producers, how many tests they recorded, their results by status, the time they spanned, up to twenty failures, their video counts (references, distinct playable recordings, unavailable) and the artifact tags, media types and labels their results carry. Filters narrow the list before `limit`: AND across the parameters, OR within one; `filters` in the answer names those that were applied and `facets` offers each dimension\'s values with counts. A server that does not know a filter ignores it and does not name it in `filters`. Send `format=markdown` for Markdown.',
347
347
  parameters: [
348
348
  { name: 'limit', in: 'query', description: 'At most this many runs (default 100).', schema: { type: 'integer', minimum: 0 } },
349
349
  { name: 'video', in: 'query', description: '`true` to list only runs that have a playable video recording. Applied to every run before `limit`; `total` counts the matches. A server that does not know it answers with every run.', schema: { type: 'boolean' } },
350
+ { name: 'artifactTag', in: 'query', description: 'Artifact tags (`demo` and `#demo` are the same), repeatable or comma-separated. A comma or backslash inside a value is written `\\,` or `\\\\`. OR within the parameter, AND with the other filters.', schema: str },
351
+ { name: 'inherited', in: 'query', description: '`1` with `artifactTag` to also match the tags attachments inherit from their check and run. Default: the exact artifact query.', schema: { type: 'boolean' } },
352
+ { name: 'type', in: 'query', description: 'Media types as roles (`video`, `image`, `audio`, `snapshot`, `text`, `file`, or a producer\'s own). `video` means a playable recording. With `artifactTag`, one artifact must be both where the report says which videos carry which tags.', schema: str },
353
+ { name: 'label', in: 'query', description: '`key=value`, or `key` for any value.', schema: str },
354
+ { name: 'environment', in: 'query', description: 'Environments or devices (`ios`, `desktop-web`).', schema: str },
355
+ { name: 'producer', in: 'query', description: 'Producers.', schema: str },
356
+ { name: 'branch', in: 'query', description: 'Branches.', schema: str },
357
+ { name: 'sha', in: 'query', description: 'Revisions: a full commit id or a prefix of at least seven characters.', schema: str },
358
+ { name: 'outcome', in: 'query', description: 'Statuses: `passed`, `failed`, `running`, `not-run`. A run\'s outcome is `failed` when any result failed, else `running`, else `passed`, else `not-run`.', schema: str },
359
+ { name: 'q', in: 'query', description: 'Words that must all appear in the title, id, spec, branch, producer, environment, tags or labels.', schema: str },
360
+ { name: 'since', in: 'query', description: 'Only records at or after this time: epoch milliseconds or an ISO date (a date alone is UTC). A result\'s recording time; a run\'s end.', schema: str },
361
+ { name: 'until', in: 'query', description: 'Only records at or before this time (same forms as `since`).', schema: str },
350
362
  { name: 'format', in: 'query', description: '`markdown` for Markdown instead of JSON.', schema: { type: 'string', enum: ['json', 'markdown'] } }
351
363
  ],
352
364
  responses: { '200': json('The runs.', 'RunList'), '304': { description: 'Unchanged since the `If-None-Match` ETag.' }, '401': UNAUTHORIZED, '503': NOT_READY }
353
365
  },
366
+ {
367
+ route: 'GET /api/reports/artifacts', path: '/api/reports/artifacts', operationId: 'getReportArtifacts', tag: 'Report', role: 'read',
368
+ summary: 'Recorded results that carry evidence, across the whole history',
369
+ description: 'Every recorded result that has attachments (recordings, screenshots, traces), newest first, found in the history and not only among the current winners: a recording tagged `demo` is found with its original run, revision and status even when a later attempt is untagged. Narrow it by artifact tag, media type, label, feature (`domain`, `spec`), environment or device, producer, branch, revision, outcome and time: AND across the parameters, OR within one; with both `artifactTag` and `type`, one artifact must be both. `filters` names the filters that were applied and `facets` offers each dimension\'s values with counts: the count beside a value is what the list would hold if that value alone were chosen in its dimension, every other filter applied. Tags written by reviewers after upload are not searched; only those in the layer are. Send `format=markdown` for Markdown.',
370
+ parameters: [
371
+ { name: 'limit', in: 'query', description: 'At most this many results (default 100).', schema: { type: 'integer', minimum: 0 } },
372
+ { name: 'offset', in: 'query', description: 'Skip this many matches (default 0).', schema: { type: 'integer', minimum: 0 } },
373
+ { name: 'video', in: 'query', description: '`true` for results with a playable video recording (the same as `type=video`).', schema: { type: 'boolean' } },
374
+ { name: 'domain', in: 'query', description: 'The feature domain of the result\'s spec.', schema: str },
375
+ { name: 'spec', in: 'query', description: 'Spec paths.', schema: str },
376
+ { name: 'artifactTag', in: 'query', description: 'Artifact tags (`demo` and `#demo` are the same), repeatable or comma-separated. A comma or backslash inside a value is written `\\,` or `\\\\`. OR within the parameter, AND with the other filters.', schema: str },
377
+ { name: 'inherited', in: 'query', description: '`1` with `artifactTag` to also match the tags attachments inherit from their check and run. Default: the exact artifact query.', schema: { type: 'boolean' } },
378
+ { name: 'type', in: 'query', description: 'Media types as roles (`video`, `image`, `audio`, `snapshot`, `text`, `file`, or a producer\'s own). `video` means a playable recording. With `artifactTag`, one artifact must be both where the report says which videos carry which tags.', schema: str },
379
+ { name: 'label', in: 'query', description: '`key=value`, or `key` for any value.', schema: str },
380
+ { name: 'environment', in: 'query', description: 'Environments or devices (`ios`, `desktop-web`).', schema: str },
381
+ { name: 'producer', in: 'query', description: 'Producers.', schema: str },
382
+ { name: 'branch', in: 'query', description: 'Branches.', schema: str },
383
+ { name: 'sha', in: 'query', description: 'Revisions: a full commit id or a prefix of at least seven characters.', schema: str },
384
+ { name: 'outcome', in: 'query', description: 'Statuses: `passed`, `failed`, `running`, `not-run`. A run\'s outcome is `failed` when any result failed, else `running`, else `passed`, else `not-run`.', schema: str },
385
+ { name: 'q', in: 'query', description: 'Words that must all appear in the title, id, spec, branch, producer, environment, tags or labels.', schema: str },
386
+ { name: 'since', in: 'query', description: 'Only records at or after this time: epoch milliseconds or an ISO date (a date alone is UTC). A result\'s recording time; a run\'s end.', schema: str },
387
+ { name: 'until', in: 'query', description: 'Only records at or before this time (same forms as `since`).', schema: str },
388
+ { name: 'format', in: 'query', description: '`markdown` for Markdown instead of JSON.', schema: { type: 'string', enum: ['json', 'markdown'] } }
389
+ ],
390
+ responses: { '200': json('The matching results and the facets.', 'ArtifactList'), '304': { description: 'Unchanged since the `If-None-Match` ETag.' }, '401': UNAUTHORIZED, '503': NOT_READY }
391
+ },
354
392
  {
355
393
  route: 'GET /api/reports/runs/detail', path: '/api/reports/runs/detail', operationId: 'getReportRun', tag: 'Report', role: 'read',
356
394
  summary: 'Every test one run recorded',
@@ -644,10 +644,22 @@ Responses: `201` The recorded entry.; `401` No token, or an unknown one. Send `A
644
644
 
645
645
  `GET /api/reports/runs` · role: read
646
646
 
647
- Every run, newest first: layers grouped by their CI run URL (else branch and commit), with their environments, producers, how many tests they recorded, their results by status, the time they spanned, up to twenty failures and their video counts (references, distinct playable recordings, unavailable). Send `format=markdown` for Markdown.
647
+ Every run, newest first: layers grouped by their CI run URL (else branch and commit), with their environments, producers, how many tests they recorded, their results by status, the time they spanned, up to twenty failures, their video counts (references, distinct playable recordings, unavailable) and the artifact tags, media types and labels their results carry. Filters narrow the list before `limit`: AND across the parameters, OR within one; `filters` in the answer names those that were applied and `facets` offers each dimension's values with counts. A server that does not know a filter ignores it and does not name it in `filters`. Send `format=markdown` for Markdown.
648
648
 
649
649
  - `limit` (query): At most this many runs (default 100).
650
650
  - `video` (query): `true` to list only runs that have a playable video recording. Applied to every run before `limit`; `total` counts the matches. A server that does not know it answers with every run.
651
+ - `artifactTag` (query): Artifact tags (`demo` and `#demo` are the same), repeatable or comma-separated. A comma or backslash inside a value is written `\,` or `\\`. OR within the parameter, AND with the other filters.
652
+ - `inherited` (query): `1` with `artifactTag` to also match the tags attachments inherit from their check and run. Default: the exact artifact query.
653
+ - `type` (query): Media types as roles (`video`, `image`, `audio`, `snapshot`, `text`, `file`, or a producer's own). `video` means a playable recording. With `artifactTag`, one artifact must be both where the report says which videos carry which tags.
654
+ - `label` (query): `key=value`, or `key` for any value.
655
+ - `environment` (query): Environments or devices (`ios`, `desktop-web`).
656
+ - `producer` (query): Producers.
657
+ - `branch` (query): Branches.
658
+ - `sha` (query): Revisions: a full commit id or a prefix of at least seven characters.
659
+ - `outcome` (query): Statuses: `passed`, `failed`, `running`, `not-run`. A run's outcome is `failed` when any result failed, else `running`, else `passed`, else `not-run`.
660
+ - `q` (query): Words that must all appear in the title, id, spec, branch, producer, environment, tags or labels.
661
+ - `since` (query): Only records at or after this time: epoch milliseconds or an ISO date (a date alone is UTC). A result's recording time; a run's end.
662
+ - `until` (query): Only records at or before this time (same forms as `since`).
651
663
  - `format` (query): `markdown` for Markdown instead of JSON.
652
664
  - `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
653
665
 
@@ -657,17 +669,87 @@ Responses: `200` The runs.; `304` Unchanged since the `If-None-Match` ETag.; `40
657
669
 
658
670
  `GET /api/projects/{project}/reports/runs` · role: read
659
671
 
660
- Every run, newest first: layers grouped by their CI run URL (else branch and commit), with their environments, producers, how many tests they recorded, their results by status, the time they spanned, up to twenty failures and their video counts (references, distinct playable recordings, unavailable). Send `format=markdown` for Markdown.
672
+ Every run, newest first: layers grouped by their CI run URL (else branch and commit), with their environments, producers, how many tests they recorded, their results by status, the time they spanned, up to twenty failures, their video counts (references, distinct playable recordings, unavailable) and the artifact tags, media types and labels their results carry. Filters narrow the list before `limit`: AND across the parameters, OR within one; `filters` in the answer names those that were applied and `facets` offers each dimension's values with counts. A server that does not know a filter ignores it and does not name it in `filters`. Send `format=markdown` for Markdown.
661
673
 
662
674
  Scoped to one project. The same project may be selected on the unprefixed API using ?project= or X-Atlas-Project. Unknown projects return 404 on reads and are created by an authenticated upload.
663
675
 
664
676
  - `project` (path, required): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
665
677
  - `limit` (query): At most this many runs (default 100).
666
678
  - `video` (query): `true` to list only runs that have a playable video recording. Applied to every run before `limit`; `total` counts the matches. A server that does not know it answers with every run.
679
+ - `artifactTag` (query): Artifact tags (`demo` and `#demo` are the same), repeatable or comma-separated. A comma or backslash inside a value is written `\,` or `\\`. OR within the parameter, AND with the other filters.
680
+ - `inherited` (query): `1` with `artifactTag` to also match the tags attachments inherit from their check and run. Default: the exact artifact query.
681
+ - `type` (query): Media types as roles (`video`, `image`, `audio`, `snapshot`, `text`, `file`, or a producer's own). `video` means a playable recording. With `artifactTag`, one artifact must be both where the report says which videos carry which tags.
682
+ - `label` (query): `key=value`, or `key` for any value.
683
+ - `environment` (query): Environments or devices (`ios`, `desktop-web`).
684
+ - `producer` (query): Producers.
685
+ - `branch` (query): Branches.
686
+ - `sha` (query): Revisions: a full commit id or a prefix of at least seven characters.
687
+ - `outcome` (query): Statuses: `passed`, `failed`, `running`, `not-run`. A run's outcome is `failed` when any result failed, else `running`, else `passed`, else `not-run`.
688
+ - `q` (query): Words that must all appear in the title, id, spec, branch, producer, environment, tags or labels.
689
+ - `since` (query): Only records at or after this time: epoch milliseconds or an ISO date (a date alone is UTC). A result's recording time; a run's end.
690
+ - `until` (query): Only records at or before this time (same forms as `since`).
667
691
  - `format` (query): `markdown` for Markdown instead of JSON.
668
692
 
669
693
  Responses: `200` The runs.; `304` Unchanged since the `If-None-Match` ETag.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
670
694
 
695
+ ### Recorded results that carry evidence, across the whole history
696
+
697
+ `GET /api/reports/artifacts` · role: read
698
+
699
+ Every recorded result that has attachments (recordings, screenshots, traces), newest first, found in the history and not only among the current winners: a recording tagged `demo` is found with its original run, revision and status even when a later attempt is untagged. Narrow it by artifact tag, media type, label, feature (`domain`, `spec`), environment or device, producer, branch, revision, outcome and time: AND across the parameters, OR within one; with both `artifactTag` and `type`, one artifact must be both. `filters` names the filters that were applied and `facets` offers each dimension's values with counts: the count beside a value is what the list would hold if that value alone were chosen in its dimension, every other filter applied. Tags written by reviewers after upload are not searched; only those in the layer are. Send `format=markdown` for Markdown.
700
+
701
+ - `limit` (query): At most this many results (default 100).
702
+ - `offset` (query): Skip this many matches (default 0).
703
+ - `video` (query): `true` for results with a playable video recording (the same as `type=video`).
704
+ - `domain` (query): The feature domain of the result's spec.
705
+ - `spec` (query): Spec paths.
706
+ - `artifactTag` (query): Artifact tags (`demo` and `#demo` are the same), repeatable or comma-separated. A comma or backslash inside a value is written `\,` or `\\`. OR within the parameter, AND with the other filters.
707
+ - `inherited` (query): `1` with `artifactTag` to also match the tags attachments inherit from their check and run. Default: the exact artifact query.
708
+ - `type` (query): Media types as roles (`video`, `image`, `audio`, `snapshot`, `text`, `file`, or a producer's own). `video` means a playable recording. With `artifactTag`, one artifact must be both where the report says which videos carry which tags.
709
+ - `label` (query): `key=value`, or `key` for any value.
710
+ - `environment` (query): Environments or devices (`ios`, `desktop-web`).
711
+ - `producer` (query): Producers.
712
+ - `branch` (query): Branches.
713
+ - `sha` (query): Revisions: a full commit id or a prefix of at least seven characters.
714
+ - `outcome` (query): Statuses: `passed`, `failed`, `running`, `not-run`. A run's outcome is `failed` when any result failed, else `running`, else `passed`, else `not-run`.
715
+ - `q` (query): Words that must all appear in the title, id, spec, branch, producer, environment, tags or labels.
716
+ - `since` (query): Only records at or after this time: epoch milliseconds or an ISO date (a date alone is UTC). A result's recording time; a run's end.
717
+ - `until` (query): Only records at or before this time (same forms as `since`).
718
+ - `format` (query): `markdown` for Markdown instead of JSON.
719
+ - `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
720
+
721
+ Responses: `200` The matching results and the facets.; `304` Unchanged since the `If-None-Match` ETag.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
722
+
723
+ ### Recorded results that carry evidence, across the whole history
724
+
725
+ `GET /api/projects/{project}/reports/artifacts` · role: read
726
+
727
+ Every recorded result that has attachments (recordings, screenshots, traces), newest first, found in the history and not only among the current winners: a recording tagged `demo` is found with its original run, revision and status even when a later attempt is untagged. Narrow it by artifact tag, media type, label, feature (`domain`, `spec`), environment or device, producer, branch, revision, outcome and time: AND across the parameters, OR within one; with both `artifactTag` and `type`, one artifact must be both. `filters` names the filters that were applied and `facets` offers each dimension's values with counts: the count beside a value is what the list would hold if that value alone were chosen in its dimension, every other filter applied. Tags written by reviewers after upload are not searched; only those in the layer are. Send `format=markdown` for Markdown.
728
+
729
+ Scoped to one project. The same project may be selected on the unprefixed API using ?project= or X-Atlas-Project. Unknown projects return 404 on reads and are created by an authenticated upload.
730
+
731
+ - `project` (path, required): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
732
+ - `limit` (query): At most this many results (default 100).
733
+ - `offset` (query): Skip this many matches (default 0).
734
+ - `video` (query): `true` for results with a playable video recording (the same as `type=video`).
735
+ - `domain` (query): The feature domain of the result's spec.
736
+ - `spec` (query): Spec paths.
737
+ - `artifactTag` (query): Artifact tags (`demo` and `#demo` are the same), repeatable or comma-separated. A comma or backslash inside a value is written `\,` or `\\`. OR within the parameter, AND with the other filters.
738
+ - `inherited` (query): `1` with `artifactTag` to also match the tags attachments inherit from their check and run. Default: the exact artifact query.
739
+ - `type` (query): Media types as roles (`video`, `image`, `audio`, `snapshot`, `text`, `file`, or a producer's own). `video` means a playable recording. With `artifactTag`, one artifact must be both where the report says which videos carry which tags.
740
+ - `label` (query): `key=value`, or `key` for any value.
741
+ - `environment` (query): Environments or devices (`ios`, `desktop-web`).
742
+ - `producer` (query): Producers.
743
+ - `branch` (query): Branches.
744
+ - `sha` (query): Revisions: a full commit id or a prefix of at least seven characters.
745
+ - `outcome` (query): Statuses: `passed`, `failed`, `running`, `not-run`. A run's outcome is `failed` when any result failed, else `running`, else `passed`, else `not-run`.
746
+ - `q` (query): Words that must all appear in the title, id, spec, branch, producer, environment, tags or labels.
747
+ - `since` (query): Only records at or after this time: epoch milliseconds or an ISO date (a date alone is UTC). A result's recording time; a run's end.
748
+ - `until` (query): Only records at or before this time (same forms as `since`).
749
+ - `format` (query): `markdown` for Markdown instead of JSON.
750
+
751
+ Responses: `200` The matching results and the facets.; `304` Unchanged since the `If-None-Match` ETag.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
752
+
671
753
  ### Every test one run recorded
672
754
 
673
755
  `GET /api/reports/runs/detail` · role: read