@crvouga/atlas 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/reports/src/catalog-health.d.ts +142 -0
- package/dist/reports/src/catalog-health.js +257 -0
- package/dist/reports/src/test-catalog.d.ts +8 -0
- package/dist/reports/src/test-catalog.js +5 -1
- package/dist/schema/src/api.d.ts +79 -0
- package/dist/schema/src/api.js +50 -0
- package/dist/server/src/app.js +9 -0
- package/dist/server/src/llms.js +1 -0
- package/dist/server/src/openapi.js +18 -2
- package/docs/reference/http-api.md +34 -0
- package/docs/reference/reports.md +224 -0
- package/docs/reference/schema.md +10 -0
- package/docs/server.md +10 -0
- package/llms-full.txt +44 -0
- package/package.json +1 -1
- package/packages/schema/schemas/catalog-health.schema.json +400 -0
- package/packages/server/openapi.json +596 -0
|
@@ -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
|
|
package/docs/reference/schema.md
CHANGED
|
@@ -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/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. |
|
|
@@ -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.
|
|
3
|
+
"version": "0.12.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",
|