@crvouga/atlas 0.11.0 → 0.13.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.
@@ -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, TestHistorySchema, RunListSchema, RunDetailSchema } 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 } from '../../schema/src/api.js';
18
18
  import { LiveRunSchema, LiveRunStartSchema, LiveRunUpdateSchema, ServerEditableSettingsSchema, ServerSettingsSnapshotSchema, 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.4.0';
@@ -35,7 +35,7 @@ const COMPONENTS = {
35
35
  PublishChecks: [PublishChecksSchema.partial({ recordedAtMs: true }), 'input'], ChecksResult: [ChecksResultSchema, 'output'],
36
36
  StoreListing: [StoreListingSchema, 'output'], StoreWriteResult: [StoreWriteResultSchema, 'output'],
37
37
  Outline: [OutlineSchema, 'output'], EvidencePage: [EvidencePageSchema, 'output'], LayerRows: [LayerRowsSchema, 'output'], CheckDetail: [CheckDetailSchema, 'output'],
38
- TestCatalog: [TestCatalogSchema, 'output'], TestHistory: [TestHistorySchema, 'output'], RunList: [RunListSchema, 'output'], RunDetail: [RunDetailSchema, 'output'],
38
+ TestCatalog: [TestCatalogSchema, 'output'], CatalogHealth: [CatalogHealthSchema, 'output'], TestHistory: [TestHistorySchema, 'output'], RunList: [RunListSchema, 'output'], RunDetail: [RunDetailSchema, 'output'],
39
39
  SpecIndex: [SpecIndexSchema, 'output'], ReportSources: [ReportSourcesSchema, 'output'], RunsIndex: [RunsIndexSchema, 'output'],
40
40
  RunEvent: [LiveRunSchema, 'output'], HelloEvent: [HelloEventSchema, 'output'], ReportEvent: [ReportEventSchema, 'output'], AtlasChangeEvent: [AtlasChangeSchema, 'output'], PingEvent: [PingEventSchema, 'output']
41
41
  };
@@ -279,6 +279,22 @@ const BASE_OPERATIONS = [
279
279
  ],
280
280
  responses: { '200': { ...json('The catalog.', 'TestCatalog'), headers: { ETag: { description: 'Changes with the report, its history and the query.', schema: str } } }, '304': { description: 'Unchanged since the `If-None-Match` ETag.' }, '401': UNAUTHORIZED, '503': NOT_READY }
281
281
  },
282
+ {
283
+ route: 'GET /api/reports/tests/health', path: '/api/reports/tests/health', operationId: 'getCatalogHealth', tag: 'Report', role: 'read',
284
+ summary: 'Why specs, domains and counts disagree',
285
+ description: [
286
+ 'Diagnoses the report\'s metadata, so a domain filter or a coverage percentage can be trusted or fixed at its source. It reads; it never changes a status, a slot or a winner.',
287
+ '',
288
+ '`counts` keeps the denominators apart: spec files, defined specs, specs with an executed test (and those without: the defined coverage gaps), tests, outlines, examples and per-environment observations. `reconciliation` walks from the Specs count to the Tests count. `findings` name ambiguous and unmatched spec paths, identical or same-titled specs, a domain shared by nearly every spec, tests with no spec, checks that fall back to their title as identity and root mappings that matched nothing.',
289
+ '',
290
+ 'Two paths are one logical spec only when a `root` mapping relates them (`from>to`: paths under `from` are the same specs as paths under `to`; an empty `from` matches every path). Without one, similar paths are reported as ambiguous and never merged. A check\'s label `outline` names the outline its example belongs to. Send `format=markdown` for Markdown.'
291
+ ].join('\n'),
292
+ parameters: [
293
+ { name: 'root', in: 'query', description: 'A root mapping `from>to`, URL-encoded. Repeatable or comma-separated.', schema: str, example: '>specs/' },
294
+ { name: 'format', in: 'query', description: '`markdown` for a Markdown report instead of JSON.', schema: { type: 'string', enum: ['json', 'markdown'] } }
295
+ ],
296
+ responses: { '200': { ...json('The diagnostics.', 'CatalogHealth'), headers: { ETag: { description: 'Changes with the report, its history and the mappings.', schema: str } } }, '304': { description: 'Unchanged since the `If-None-Match` ETag.' }, '401': UNAUTHORIZED, '503': NOT_READY }
297
+ },
282
298
  {
283
299
  route: 'GET /api/reports/tests/history', path: '/api/reports/tests/history', operationId: 'getTestHistory', tag: 'Report', role: 'read',
284
300
  summary: 'One test and every result recorded for it',
@@ -504,6 +504,40 @@ Scoped to one project. The same project may be selected on the unprefixed API us
504
504
 
505
505
  Responses: `200` The catalog.; `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`.
506
506
 
507
+ ### Why specs, domains and counts disagree
508
+
509
+ `GET /api/reports/tests/health` · role: read
510
+
511
+ Diagnoses the report's metadata, so a domain filter or a coverage percentage can be trusted or fixed at its source. It reads; it never changes a status, a slot or a winner.
512
+
513
+ `counts` keeps the denominators apart: spec files, defined specs, specs with an executed test (and those without: the defined coverage gaps), tests, outlines, examples and per-environment observations. `reconciliation` walks from the Specs count to the Tests count. `findings` name ambiguous and unmatched spec paths, identical or same-titled specs, a domain shared by nearly every spec, tests with no spec, checks that fall back to their title as identity and root mappings that matched nothing.
514
+
515
+ Two paths are one logical spec only when a `root` mapping relates them (`from>to`: paths under `from` are the same specs as paths under `to`; an empty `from` matches every path). Without one, similar paths are reported as ambiguous and never merged. A check's label `outline` names the outline its example belongs to. Send `format=markdown` for Markdown.
516
+
517
+ - `root` (query): A root mapping `from>to`, URL-encoded. Repeatable or comma-separated.
518
+ - `format` (query): `markdown` for a Markdown report instead of JSON.
519
+ - `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
520
+
521
+ Responses: `200` The diagnostics.; `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`.
522
+
523
+ ### Why specs, domains and counts disagree
524
+
525
+ `GET /api/projects/{project}/reports/tests/health` · role: read
526
+
527
+ Diagnoses the report's metadata, so a domain filter or a coverage percentage can be trusted or fixed at its source. It reads; it never changes a status, a slot or a winner.
528
+
529
+ `counts` keeps the denominators apart: spec files, defined specs, specs with an executed test (and those without: the defined coverage gaps), tests, outlines, examples and per-environment observations. `reconciliation` walks from the Specs count to the Tests count. `findings` name ambiguous and unmatched spec paths, identical or same-titled specs, a domain shared by nearly every spec, tests with no spec, checks that fall back to their title as identity and root mappings that matched nothing.
530
+
531
+ Two paths are one logical spec only when a `root` mapping relates them (`from>to`: paths under `from` are the same specs as paths under `to`; an empty `from` matches every path). Without one, similar paths are reported as ambiguous and never merged. A check's label `outline` names the outline its example belongs to. Send `format=markdown` for Markdown.
532
+
533
+ 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.
534
+
535
+ - `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.
536
+ - `root` (query): A root mapping `from>to`, URL-encoded. Repeatable or comma-separated.
537
+ - `format` (query): `markdown` for a Markdown report instead of JSON.
538
+
539
+ Responses: `200` The diagnostics.; `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`.
540
+
507
541
  ### One test and every result recorded for it
508
542
 
509
543
  `GET /api/reports/tests/history` · role: read
@@ -330,6 +330,227 @@ export async function readBundleIndex(
330
330
 
331
331
  Reads and parses the bundle index at `key`; an index that is not there is an empty bundle (`{}`). Invalid JSON throws.
332
332
 
333
+ ### Catalog health: spec identity, domains and denominators
334
+
335
+ Source: `packages/reports/src/catalog-health.ts`. Import: `@crvouga/atlas/reports/catalog-health`
336
+
337
+ Explains why the Specs and the Tests of a report may disagree, and what a producer or an operator can change about it.
338
+ `catalogHealth` reads a `CatalogModel` (the tests of a report and the specs it is laid over) and writes a
339
+ `CatalogHealth` document:
340
+
341
+ - **Spec identity.** The same promise can reach Atlas under two paths (`checkout/cart.feature` published,
342
+ `specs/checkout/cart.feature` recorded). Atlas joins two paths only when a declared root mapping (`RootMapping`)
343
+ relates them, and then shows one logical spec with its original paths. Without a mapping it never merges by a
344
+ similar title or file name: it reports the pair as ambiguous and names the candidates.
345
+ - **Domains.** A domain is whatever the spec's metadata says. When almost every spec shares one value, often the root
346
+ folder, the document says so; it never replaces explicit metadata.
347
+ - **Denominators.** Defined specs, specs with an executed test, tests (check identities), examples and per-environment
348
+ observations are different numbers. `reconciliation` walks from the number the Specs view shows to the number the
349
+ Tests view groups by, step by step, and `counts.scenarios` tells one outline, its examples and their observations apart.
350
+ - **Fallback identity.** A check whose id is its title has no stable id; renaming it starts a new check. Counted, not
351
+ guessed at.
352
+
353
+ It only reads: it never changes a status, a slot or a winner, so it cannot disagree with the report it describes.
354
+ Optional label `outline` on a check names the scenario outline its example belongs to.
355
+
356
+ See docs/server.md#reading-the-report-as-an-agent
357
+
358
+ #### `CATALOG_HEALTH_VERSION`
359
+
360
+ ```ts
361
+ export const CATALOG_HEALTH_VERSION = …
362
+ ```
363
+
364
+ The version of the `CatalogHealth` document. Only optional fields are added within it.
365
+
366
+ #### `HEALTH_ITEM_LIMIT`
367
+
368
+ ```ts
369
+ export const HEALTH_ITEM_LIMIT = …
370
+ ```
371
+
372
+ The most paths listed in one finding; its `count` is always the whole.
373
+
374
+ #### `RootMapping`
375
+
376
+ ```ts
377
+ export type RootMapping = { readonly from: string; readonly to: string };
378
+ ```
379
+
380
+ Declares that paths under `from` are the same specs as paths under `to`, e.g. `{ from: '', to: 'specs/' }`. Empty `from` matches every path.
381
+
382
+ #### `HealthItem`
383
+
384
+ ```ts
385
+ export type HealthItem = {
386
+ readonly path: string;
387
+ /** Other paths it could be the same promise as, when any; Atlas has not merged them. */
388
+ readonly candidates?: readonly { readonly path: string; readonly reason: string }[];
389
+ };
390
+ ```
391
+
392
+ One thing worth a look.
393
+
394
+ #### `HealthCode`
395
+
396
+ ```ts
397
+ export type HealthCode =
398
+ | 'ambiguous-spec-path'
399
+ | 'unmatched-spec-path'
400
+ | 'duplicate-spec-content'
401
+ | 'duplicate-spec-title'
402
+ | 'generic-domain'
403
+ | 'tests-without-spec'
404
+ | 'no-spec-definitions'
405
+ | 'fallback-identity'
406
+ | 'unused-root-mapping'
407
+ | 'spec-without-execution'
408
+ | 'historical-only';
409
+ ```
410
+
411
+ Codes of `HealthFinding.code`. The set grows; a reader treats an unknown code as informational.
412
+
413
+ #### `HealthFinding`
414
+
415
+ ```ts
416
+ export type HealthFinding = {
417
+ readonly code: HealthCode;
418
+ /** `warning` is something that makes a count or a filter misleading; `info` explains a number. */
419
+ readonly severity: 'warning' | 'info';
420
+ readonly message: string;
421
+ /** How many paths, specs or tests it affects. */
422
+ readonly count: number;
423
+ /** Up to `HEALTH_ITEM_LIMIT` of them. */
424
+ readonly items: readonly HealthItem[];
425
+ };
426
+ ```
427
+
428
+ A finding: what is inconsistent, how many it affects, and what to change.
429
+
430
+ #### `SpecAlias`
431
+
432
+ ```ts
433
+ export type SpecAlias = {
434
+ /** The logical spec's path after mapping. */
435
+ readonly path: string;
436
+ /** Every original path it was seen under, sorted. */
437
+ readonly sources: readonly string[];
438
+ };
439
+ ```
440
+
441
+ A spec that reached Atlas under more than one path and was joined by a declared root mapping.
442
+
443
+ #### `ReconciliationStep`
444
+
445
+ ```ts
446
+ export type ReconciliationStep = {
447
+ readonly code: 'merged-by-mapping' | 'defined-without-results' | 'results-without-definition';
448
+ readonly label: string;
449
+ /** The change to the running total: negative removes specs, positive adds them. */
450
+ readonly delta: number;
451
+ };
452
+ ```
453
+
454
+ One step from the Specs count to the Tests count.
455
+
456
+ #### `CatalogHealth`
457
+
458
+ ```ts
459
+ export type CatalogHealth = {
460
+ readonly schemaVersion: typeof CATALOG_HEALTH_VERSION;
461
+ readonly version: string;
462
+ readonly generatedAtMs: number;
463
+ /** True when there is no warning. Informational findings do not count. */
464
+ readonly ok: boolean;
465
+ readonly counts: {
466
+ readonly specs: {
467
+ /** Spec files the report is laid over (before mapping). */
468
+ readonly files: number;
469
+ /** Logical specs among them: files joined by a root mapping count once. */
470
+ readonly defined: number;
471
+ /** Logical specs, defined or not, with at least one test. */
472
+ readonly withTests: number;
473
+ /** Defined logical specs with a test that has a passed or failed result. They are the denominator of execution coverage. */
474
+ readonly executed: number;
475
+ /** Defined logical specs with no test that has a passed or failed result: the defined coverage gaps. */
476
+ readonly withoutExecution: number;
477
+ };
478
+ readonly tests: {
479
+ readonly total: number;
480
+ readonly withSpec: number;
481
+ readonly withoutSpec: number;
482
+ /** Tests whose current status is `passed`. A definition with no test never adds to it. */
483
+ readonly passed: number;
484
+ /** Tests the history holds but the report has no current result for. */
485
+ readonly historicalOnly: number;
486
+ };
487
+ readonly scenarios: {
488
+ /** Outlines: tests sharing the label `outline` count once; a test without it is its own outline. */
489
+ readonly outlines: number;
490
+ /** Concrete examples: tests (a check identity), however many environments ran them. */
491
+ readonly examples: number;
492
+ /** Current results per producer, environment and parameter set. */
493
+ readonly observations: number;
494
+ /** Observations that passed or failed. */
495
+ readonly executedObservations: number;
496
+ readonly passedObservations: number;
497
+ };
498
+ };
499
+ …
500
+ ```
501
+
502
+ The `GET /api/reports/tests/health` document.
503
+
504
+ #### `parseRootMapping`
505
+
506
+ ```ts
507
+ export function parseRootMapping(text: string): RootMapping | null
508
+ ```
509
+
510
+ Parses `from>to` (as `?root=` gives it) into a mapping. Returns null when there is no `>`. Whitespace around each side is trimmed.
511
+
512
+ #### `parseRootMappings`
513
+
514
+ ```ts
515
+ export function parseRootMappings(params: URLSearchParams): RootMapping[]
516
+ ```
517
+
518
+ Reads the repeatable `root` search parameter: each value `from>to`, several may be comma-separated. Values without `>` are ignored.
519
+
520
+ #### `canonicalSpecPath`
521
+
522
+ ```ts
523
+ export function canonicalSpecPath(path: string, roots: readonly RootMapping[]): { readonly path: string; readonly mapping: RootMapping | null }
524
+ ```
525
+
526
+ The logical path of a spec path: the path Atlas already normalises (`specPath`), after the first mapping whose `from`
527
+ is a prefix of it (the longest `from` wins). A path already under `to` is left alone. Returns the path and the mapping
528
+ that changed it, if any.
529
+
530
+ #### `catalogHealth`
531
+
532
+ ```ts
533
+ export function catalogHealth(model: CatalogModel, roots: readonly RootMapping[] = []): CatalogHealth
534
+ ```
535
+
536
+ Diagnoses a report's spec identity, domains and counts. `roots` are the declared path-root mappings. Pure: the same
537
+ model and mappings give the same document, and nothing in it changes what the report says passed.
538
+
539
+ **Example**
540
+
541
+ ```ts
542
+ const health = catalogHealth(model, [{ from: '', to: 'specs/' }]);
543
+ if (!health.ok) console.log(healthMarkdown(health));
544
+ ```
545
+
546
+ #### `healthMarkdown`
547
+
548
+ ```ts
549
+ export function healthMarkdown(health: CatalogHealth): string
550
+ ```
551
+
552
+ The health document as Markdown, for an agent or a terminal.
553
+
333
554
  ### Catalog search, filter, group and sort
334
555
 
335
556
  Source: `packages/reports/src/catalog.ts`. Import: `@crvouga/atlas/reports/catalog`
@@ -7564,6 +7785,7 @@ const catalog = testCatalog(catalogModel(layers, { history }));
7564
7785
 
7565
7786
  - `get rev(): number { return this.#rev; }` — Changes each time a layer is added.
7566
7787
  - `get specs(): RecordedSpecs { return this.#specs; }` — The specs the added layers recorded.
7788
+ - `tests(): readonly { readonly key: string; readonly id: string; readonly specPath: string | null }[]` — Every test the history holds a result for (keyed as `TestSummary.key`), including tests the report no longer has a current result for.
7567
7789
  - `get layers(): number { return this.#seen.size; }` — How many layers were added.
7568
7790
  - `add(layer: LayerHeader, rows: readonly LayerRow[], specs: readonly SpecEntry[] = [], backend = layer.backend): void` — Takes in one layer: its header, its rows, the specs it recorded and the backend it was read from. A layer id already added is ignored.
7569
7791
  - `of(key: string): readonly TestResultEntry[]` — The recorded results of one test (by `TestSummary.key`), newest first. Empty when none.
@@ -7616,6 +7838,8 @@ export type CatalogModel = {
7616
7838
  readonly facets: CatalogFacets;
7617
7839
  readonly haystacks: ReadonlyMap<string, string>;
7618
7840
  readonly history: TestHistory | null;
7841
+ /** The specs the report is laid over, sorted by path: the given ones, else those the layers recorded. What `catalogHealth` calls defined. Absent on a model built from a server's catalog document. */
7842
+ readonly specs?: readonly SpecEntry[];
7619
7843
  };
7620
7844
  ```
7621
7845
 
@@ -234,6 +234,16 @@ export const TestCatalogSchema = …
234
234
  `GET /api/reports/tests`: every test of the report with a summary, insights and facets. The summary and facets cover
235
235
  every test; `tests` is the page of those the query matched.
236
236
 
237
+ #### `CatalogHealthSchema`
238
+
239
+ ```ts
240
+ export const CatalogHealthSchema = …
241
+ ```
242
+
243
+ `GET /api/reports/tests/health`: why the report's Specs, domains and counts may disagree. Counts keep the denominators
244
+ apart (spec files, defined specs, specs with an executed test, tests, outlines, examples, observations), `reconciliation`
245
+ walks from one to another, and `findings` name what to fix. It reads the report; it never changes what passed.
246
+
237
247
  #### `TestHistorySchema`
238
248
 
239
249
  ```ts
package/docs/server.md CHANGED
@@ -255,6 +255,7 @@ with `format=markdown` (or `Accept: text/markdown`), and takes the project prefi
255
255
  | Every test, a summary (by status, environment, producer, domain), insights (failing, flaky, slowest) and facets | `GET /api/reports/tests` |
256
256
  | Only some: `q` (all words), `status` (or `flaky`), `environment`, `producer`, `domain`, `spec`, `tag`, `run`; `sort`, `offset`, `limit` | `GET /api/reports/tests?status=failed&environment=ios` |
257
257
  | One test: every recorded result (status, duration, environment, branch, commit, CI run), newest first | `GET /api/reports/tests/history?key=<key>` |
258
+ | Why Specs, domains and counts disagree: ambiguous spec paths, shared domains, gaps, a reconciliation of the totals | `GET /api/reports/tests/health` |
258
259
  | The runs that recorded the report (by CI run URL, else branch and commit) | `GET /api/reports/runs` |
259
260
  | Every test one run recorded | `GET /api/reports/runs/detail?key=<run key>` |
260
261
 
@@ -267,6 +268,13 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
267
268
  `stats` describes its recorded history: pass rate, flips, whether it is flaky and why (it passed and failed on the
268
269
  same commit, passed only after a retry, or its producer said so), durations (avg, p50, p95, min, max, last), runs
269
270
  and branches.
271
+ - The **health** document explains the numbers instead of adding one. It keeps spec files, defined specs, specs with an
272
+ executed test, tests, outlines, examples and per-environment observations apart, and walks from the Specs total to
273
+ the Tests total. Two spec paths are one logical spec only when a mapping says so
274
+ (`?root=<from>><to>`, for example `?root=%3Especs/` to put `checkout/cart.feature` under `specs/`); similar paths
275
+ are reported as ambiguous and never merged, and a similar title is never an identity. Give a check the label
276
+ `outline` to say which scenario outline an example belongs to. A domain shared by nearly every spec is flagged, but
277
+ explicit domain metadata always stays authoritative. The document only reads: it changes no status.
270
278
  - The server keeps the newest 100 results of every test (progress reported as `running` is not history) and every run's last result per test, in memory, as layers
271
279
  arrive; it is rebuilt from the layers on restart. History never changes a result.
272
280
  - Without a published spec set, tests are grouped under the specs their layers recorded (`specs[]`: path, domain,
@@ -318,6 +326,8 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
318
326
  | `DELETE /api/projects/{project}/store/{key}` | publish | Remove a scratch object. |
319
327
  | `GET /api/reports/tests` | read | Every test, with insights, facets and history statistics. |
320
328
  | `GET /api/projects/{project}/reports/tests` | read | Every test, with insights, facets and history statistics. |
329
+ | `GET /api/reports/tests/health` | read | Why specs, domains and counts disagree. |
330
+ | `GET /api/projects/{project}/reports/tests/health` | read | Why specs, domains and counts disagree. |
321
331
  | `GET /api/reports/tests/history` | read | One test and every result recorded for it. |
322
332
  | `GET /api/projects/{project}/reports/tests/history` | read | One test and every result recorded for it. |
323
333
  | `GET /api/reports/runs` | read | The runs that recorded the report. |
package/docs/ui.md CHANGED
@@ -119,7 +119,7 @@ The narrowly scoped exceptions are:
119
119
  | --- | --- | --- |
120
120
  | Shared shell | Project switching, spec navigation, run selection, connection status, diagnostics, mobile index | AppShell, Burger, NavLink, Menu, Popover, NativeSelect, Drawer |
121
121
  | Project home | Tests/runs tabs, metrics, search, status/environment/domain filters, grouping, sorting, show more | Container, Tabs, SimpleGrid, Paper, TextInput, Button, Accordion |
122
- | Test detail | Current outcomes, statistics, duration graph, history, artifacts, recording | Drawer, Table, Badge, Alert, Stage |
122
+ | Test detail | Current outcomes, statistics, duration graph, history, artifacts, recording; the `layer` address parameter pins it to one run's result, labelled "This run" against "Latest combined evidence" | Drawer, Table, Badge, Alert, Stage |
123
123
  | Specs | All six existing kinds, overview/index, Gherkin rules/scenarios/examples, Quint declarations/traces, DMN tables, source text | SpecHeader, Accordion, Table, CodeHighlightTabs, ResultRow |
124
124
  | Statecharts | Graph, screen/event/journey details, developer metadata, chart test results | Mantine cards/navigation and product compositions; graph exception above |
125
125
  | Evidence | Steps, retries, annotations, facts, unknown extensions, safe downloads and previews | List, Badge, Accordion, Code, Image, native media |
package/llms-full.txt CHANGED
@@ -1435,6 +1435,7 @@ with `format=markdown` (or `Accept: text/markdown`), and takes the project prefi
1435
1435
  | Every test, a summary (by status, environment, producer, domain), insights (failing, flaky, slowest) and facets | `GET /api/reports/tests` |
1436
1436
  | Only some: `q` (all words), `status` (or `flaky`), `environment`, `producer`, `domain`, `spec`, `tag`, `run`; `sort`, `offset`, `limit` | `GET /api/reports/tests?status=failed&environment=ios` |
1437
1437
  | One test: every recorded result (status, duration, environment, branch, commit, CI run), newest first | `GET /api/reports/tests/history?key=<key>` |
1438
+ | Why Specs, domains and counts disagree: ambiguous spec paths, shared domains, gaps, a reconciliation of the totals | `GET /api/reports/tests/health` |
1438
1439
  | The runs that recorded the report (by CI run URL, else branch and commit) | `GET /api/reports/runs` |
1439
1440
  | Every test one run recorded | `GET /api/reports/runs/detail?key=<run key>` |
1440
1441
 
@@ -1447,6 +1448,13 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
1447
1448
  `stats` describes its recorded history: pass rate, flips, whether it is flaky and why (it passed and failed on the
1448
1449
  same commit, passed only after a retry, or its producer said so), durations (avg, p50, p95, min, max, last), runs
1449
1450
  and branches.
1451
+ - The **health** document explains the numbers instead of adding one. It keeps spec files, defined specs, specs with an
1452
+ executed test, tests, outlines, examples and per-environment observations apart, and walks from the Specs total to
1453
+ the Tests total. Two spec paths are one logical spec only when a mapping says so
1454
+ (`?root=<from>><to>`, for example `?root=%3Especs/` to put `checkout/cart.feature` under `specs/`); similar paths
1455
+ are reported as ambiguous and never merged, and a similar title is never an identity. Give a check the label
1456
+ `outline` to say which scenario outline an example belongs to. A domain shared by nearly every spec is flagged, but
1457
+ explicit domain metadata always stays authoritative. The document only reads: it changes no status.
1450
1458
  - The server keeps the newest 100 results of every test (progress reported as `running` is not history) and every run's last result per test, in memory, as layers
1451
1459
  arrive; it is rebuilt from the layers on restart. History never changes a result.
1452
1460
  - Without a published spec set, tests are grouped under the specs their layers recorded (`specs[]`: path, domain,
@@ -1498,6 +1506,8 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
1498
1506
  | `DELETE /api/projects/{project}/store/{key}` | publish | Remove a scratch object. |
1499
1507
  | `GET /api/reports/tests` | read | Every test, with insights, facets and history statistics. |
1500
1508
  | `GET /api/projects/{project}/reports/tests` | read | Every test, with insights, facets and history statistics. |
1509
+ | `GET /api/reports/tests/health` | read | Why specs, domains and counts disagree. |
1510
+ | `GET /api/projects/{project}/reports/tests/health` | read | Why specs, domains and counts disagree. |
1501
1511
  | `GET /api/reports/tests/history` | read | One test and every result recorded for it. |
1502
1512
  | `GET /api/projects/{project}/reports/tests/history` | read | One test and every result recorded for it. |
1503
1513
  | `GET /api/reports/runs` | read | The runs that recorded the report. |
@@ -3779,7 +3789,7 @@ The narrowly scoped exceptions are:
3779
3789
  | --- | --- | --- |
3780
3790
  | Shared shell | Project switching, spec navigation, run selection, connection status, diagnostics, mobile index | AppShell, Burger, NavLink, Menu, Popover, NativeSelect, Drawer |
3781
3791
  | Project home | Tests/runs tabs, metrics, search, status/environment/domain filters, grouping, sorting, show more | Container, Tabs, SimpleGrid, Paper, TextInput, Button, Accordion |
3782
- | Test detail | Current outcomes, statistics, duration graph, history, artifacts, recording | Drawer, Table, Badge, Alert, Stage |
3792
+ | Test detail | Current outcomes, statistics, duration graph, history, artifacts, recording; the `layer` address parameter pins it to one run's result, labelled "This run" against "Latest combined evidence" | Drawer, Table, Badge, Alert, Stage |
3783
3793
  | Specs | All six existing kinds, overview/index, Gherkin rules/scenarios/examples, Quint declarations/traces, DMN tables, source text | SpecHeader, Accordion, Table, CodeHighlightTabs, ResultRow |
3784
3794
  | Statecharts | Graph, screen/event/journey details, developer metadata, chart test results | Mantine cards/navigation and product compositions; graph exception above |
3785
3795
  | Evidence | Steps, retries, annotations, facts, unknown extensions, safe downloads and previews | List, Badge, Accordion, Code, Image, native media |
@@ -4805,6 +4815,40 @@ Scoped to one project. The same project may be selected on the unprefixed API us
4805
4815
 
4806
4816
  Responses: `200` The catalog.; `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`.
4807
4817
 
4818
+ ### Why specs, domains and counts disagree
4819
+
4820
+ `GET /api/reports/tests/health` · role: read
4821
+
4822
+ Diagnoses the report's metadata, so a domain filter or a coverage percentage can be trusted or fixed at its source. It reads; it never changes a status, a slot or a winner.
4823
+
4824
+ `counts` keeps the denominators apart: spec files, defined specs, specs with an executed test (and those without: the defined coverage gaps), tests, outlines, examples and per-environment observations. `reconciliation` walks from the Specs count to the Tests count. `findings` name ambiguous and unmatched spec paths, identical or same-titled specs, a domain shared by nearly every spec, tests with no spec, checks that fall back to their title as identity and root mappings that matched nothing.
4825
+
4826
+ Two paths are one logical spec only when a `root` mapping relates them (`from>to`: paths under `from` are the same specs as paths under `to`; an empty `from` matches every path). Without one, similar paths are reported as ambiguous and never merged. A check's label `outline` names the outline its example belongs to. Send `format=markdown` for Markdown.
4827
+
4828
+ - `root` (query): A root mapping `from>to`, URL-encoded. Repeatable or comma-separated.
4829
+ - `format` (query): `markdown` for a Markdown report instead of JSON.
4830
+ - `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
4831
+
4832
+ Responses: `200` The diagnostics.; `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`.
4833
+
4834
+ ### Why specs, domains and counts disagree
4835
+
4836
+ `GET /api/projects/{project}/reports/tests/health` · role: read
4837
+
4838
+ Diagnoses the report's metadata, so a domain filter or a coverage percentage can be trusted or fixed at its source. It reads; it never changes a status, a slot or a winner.
4839
+
4840
+ `counts` keeps the denominators apart: spec files, defined specs, specs with an executed test (and those without: the defined coverage gaps), tests, outlines, examples and per-environment observations. `reconciliation` walks from the Specs count to the Tests count. `findings` name ambiguous and unmatched spec paths, identical or same-titled specs, a domain shared by nearly every spec, tests with no spec, checks that fall back to their title as identity and root mappings that matched nothing.
4841
+
4842
+ Two paths are one logical spec only when a `root` mapping relates them (`from>to`: paths under `from` are the same specs as paths under `to`; an empty `from` matches every path). Without one, similar paths are reported as ambiguous and never merged. A check's label `outline` names the outline its example belongs to. Send `format=markdown` for Markdown.
4843
+
4844
+ 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.
4845
+
4846
+ - `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.
4847
+ - `root` (query): A root mapping `from>to`, URL-encoded. Repeatable or comma-separated.
4848
+ - `format` (query): `markdown` for a Markdown report instead of JSON.
4849
+
4850
+ Responses: `200` The diagnostics.; `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`.
4851
+
4808
4852
  ### One test and every result recorded for it
4809
4853
 
4810
4854
  `GET /api/reports/tests/history` · role: read
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crvouga/atlas",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Atlas: specs of any kind, reported as layered evidence. Publish checks from any runner (JUnit, CTRF, a script), combine them across nodes, branches, environments and backends, gate CI on the result, and run statecharts against the real app.",
5
5
  "license": "MIT",
6
6
  "type": "module",