@crvouga/atlas 1.7.0 → 1.9.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 +13 -1
- package/dist/reports/src/evidence-detail.js +6 -1
- package/dist/reports/src/evidence-types.d.ts +2 -0
- package/dist/reports/src/explorer-types.d.ts +19 -0
- package/dist/reports/src/latency.d.ts +204 -0
- package/dist/reports/src/latency.js +219 -0
- package/dist/reports/src/reliability.d.ts +181 -0
- package/dist/reports/src/reliability.js +294 -0
- package/dist/reports/src/report-index.js +1 -1
- package/dist/reports/src/reports-explorer-route.d.ts +19 -6
- package/dist/reports/src/reports-route.d.ts +19 -6
- package/dist/reports/src/test-catalog.d.ts +11 -3
- package/dist/reports/src/test-catalog.js +12 -5
- package/dist/schema/src/api.d.ts +377 -0
- package/dist/schema/src/api.js +55 -3
- package/dist/schema/src/layer.d.ts +90 -0
- package/dist/schema/src/layer.js +20 -1
- package/dist/server/src/app.js +15 -0
- package/dist/server/src/llms.js +1 -0
- package/dist/server/src/openapi.js +25 -2
- package/docs/extending.md +1 -1
- package/docs/reference/http-api.md +48 -0
- package/docs/reference/reports.md +619 -1
- package/docs/reference/schema.md +39 -0
- package/docs/rich-evidence.md +43 -0
- package/docs/server.md +18 -0
- package/docs/ui.md +20 -1
- package/llms-full.txt +130 -2
- package/package.json +1 -1
- package/packages/schema/schemas/check-detail.schema.json +56 -1
- package/packages/schema/schemas/effective-delta.schema.json +86 -2
- package/packages/schema/schemas/effective-evidence.schema.json +86 -2
- package/packages/schema/schemas/evidence-page.schema.json +13 -0
- package/packages/schema/schemas/layer-rows.schema.json +73 -2
- package/packages/schema/schemas/layer.schema.json +112 -2
- package/packages/schema/schemas/publish-checks.schema.json +56 -1
- package/packages/schema/schemas/test-catalog.schema.json +102 -0
- package/packages/schema/schemas/test-history.schema.json +204 -0
- package/packages/schema/schemas/test-reliability.schema.json +629 -0
- package/packages/server/openapi.json +2354 -661
- package/packages/visualizer/src/evidence/check-detail.tsx +9 -0
- package/packages/visualizer/src/explorer/ProductLatency.tsx +214 -0
- package/packages/visualizer/src/explorer/Reliability.tsx +337 -0
- package/packages/visualizer/src/explorer/TestDetail.tsx +12 -2
- package/packages/visualizer/src/explorer/catalog.tsx +1 -1
- package/packages/visualizer/src/explorer/search.ts +36 -0
package/dist/schema/src/api.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* @see docs/server.md
|
|
15
15
|
*/
|
|
16
16
|
import { z } from 'zod/v4';
|
|
17
|
-
import { CheckSchema, EvidenceScopeSchema, LayerRunSchema } from './layer.js';
|
|
17
|
+
import { CheckSchema, EvidenceScopeSchema, LayerRunSchema, MeasurementSchema } from './layer.js';
|
|
18
18
|
/** One of the statuses a check has in a report: `passed`, `failed`, `running` (not finished) or `not-run`. Closed. */
|
|
19
19
|
export const EvidenceStatusSchema = z.enum(['passed', 'failed', 'running', 'not-run']);
|
|
20
20
|
/**
|
|
@@ -85,6 +85,7 @@ export const EvidenceSummarySchema = z.looseObject({
|
|
|
85
85
|
steps: z.number().int().nonnegative(),
|
|
86
86
|
annotations: z.number().int().nonnegative(),
|
|
87
87
|
attempts: z.number().int().nonnegative(),
|
|
88
|
+
attemptStatuses: z.array(EvidenceStatusSchema).optional().describe('The status of each recorded try, in the order they were made. Absent when no try was recorded, and in reports written before this was recorded: a first try is then unknown, never assumed to have passed'),
|
|
88
89
|
links: z.number().int().nonnegative(),
|
|
89
90
|
tags: z.array(z.string()),
|
|
90
91
|
artifactTags: z.array(z.string()).optional().describe('Normalized tags of the attachments themselves, sorted and distinct. Absent when no attachment is tagged; never inherited from a check or run'),
|
|
@@ -124,7 +125,7 @@ export const LayerRowSchema = z.looseObject({
|
|
|
124
125
|
status: EvidenceStatusSchema,
|
|
125
126
|
specPath: z.string().nullable().describe('The spec the check belongs to, `specs/<path>`, or null'),
|
|
126
127
|
detail: z.string().describe('Why it has this status; empty when there is nothing to add'),
|
|
127
|
-
measurements: z.array(
|
|
128
|
+
measurements: z.array(MeasurementSchema),
|
|
128
129
|
testId: z.string().nullable(),
|
|
129
130
|
variant: z.record(z.string(), z.string()),
|
|
130
131
|
durationMs: z.number().nullable(),
|
|
@@ -270,7 +271,9 @@ export const TestResultEntrySchema = z.looseObject({
|
|
|
270
271
|
layer: z.string().describe('The id of the layer that recorded it'),
|
|
271
272
|
backend: z.string(),
|
|
272
273
|
detail: z.string().describe('Why it has its status, at most 500 characters'),
|
|
273
|
-
|
|
274
|
+
specRevision: z.string().optional().describe('The fingerprint of its spec text that the layer recorded, when it recorded one. Two results with different revisions were recorded against different promises. Absent: not recorded, never assumed to match'),
|
|
275
|
+
evidence: EvidenceSummarySchema.optional(),
|
|
276
|
+
latency: z.array(MeasurementSchema).optional().describe('Its product-latency measurements (`kind: product-latency`), when it recorded any. Separate from `durationMs`, which is how long the test took')
|
|
274
277
|
});
|
|
275
278
|
const DurationStatsSchema = z.looseObject({
|
|
276
279
|
count: z.number().int().nonnegative(), totalMs: z.number(), avgMs: z.number(), p50Ms: z.number(), p95Ms: z.number(), minMs: z.number(), maxMs: z.number(), lastMs: z.number()
|
|
@@ -389,6 +392,55 @@ export const TestHistorySchema = z.looseObject({
|
|
|
389
392
|
test: TestSummarySchema,
|
|
390
393
|
history: z.array(TestResultEntrySchema)
|
|
391
394
|
});
|
|
395
|
+
const ReliabilityRateSchema = z.looseObject({
|
|
396
|
+
executions: z.number().int().nonnegative().describe('Independent executions that passed or failed'),
|
|
397
|
+
passed: z.number().int().nonnegative(),
|
|
398
|
+
failed: z.number().int().nonnegative(),
|
|
399
|
+
rate: z.number().nullable().describe('passed / executions, or null with none')
|
|
400
|
+
});
|
|
401
|
+
/**
|
|
402
|
+
* `GET /api/reports/tests/reliability`: how reliable one test is inside a scope, and exactly how that figure was counted.
|
|
403
|
+
* The snapshots one run recorded for one producer, environment and parameter set are one independent execution; only
|
|
404
|
+
* `passed` and `failed` executions are trials; `notes` say when the sample is too small, pooled, selectively rerun or
|
|
405
|
+
* bounded. It reads results and never changes a status. It is not a production failure probability.
|
|
406
|
+
*/
|
|
407
|
+
export const TestReliabilitySchema = z.looseObject({
|
|
408
|
+
schemaVersion: z.literal(1),
|
|
409
|
+
key: z.string().describe('The test\'s key'),
|
|
410
|
+
scope: z.looseObject({
|
|
411
|
+
environment: z.array(z.string()).optional(), producer: z.array(z.string()).optional(), parameter: z.array(z.string()).optional(),
|
|
412
|
+
branch: z.array(z.string()).optional(), sha: z.array(z.string()).optional(), specRevision: z.array(z.string()).optional(),
|
|
413
|
+
revision: z.enum(['current', 'any']).optional(), since: z.number().optional(), until: z.number().optional()
|
|
414
|
+
}).describe('What was asked: the selection the figures are about'),
|
|
415
|
+
minimumExecutions: z.number().int().positive().describe('Completed executions below which the sample is `insufficient`'),
|
|
416
|
+
sample: z.enum(['none', 'insufficient', 'sufficient']).describe('A count only: it says nothing about whether the executions are representative; read `notes`'),
|
|
417
|
+
eventual: ReliabilityRateSchema.describe('Executions that passed in the end, over those that passed or failed'),
|
|
418
|
+
interval: z.looseObject({ method: z.literal('wilson'), level: z.number(), lower: z.number(), upper: z.number(), assumptions: z.string() }).nullable().describe('An interval estimate with the assumptions it needs; null without an execution'),
|
|
419
|
+
firstAttempt: ReliabilityRateSchema.describe('Over the executions that recorded their tries: those whose first try passed'),
|
|
420
|
+
unrecordedAttempts: z.number().int().nonnegative().describe('Counted executions that recorded no tries: their first try is unknown, never assumed to have passed'),
|
|
421
|
+
attempts: z.looseObject({ total: z.number().int().nonnegative(), retried: z.number().int().nonnegative(), recovered: z.number().int().nonnegative() }),
|
|
422
|
+
currentRevision: z.string().nullable().describe('The spec revision `revision=current` counted, or null'),
|
|
423
|
+
historical: z.looseObject({
|
|
424
|
+
executions: z.number().int().nonnegative(), passed: z.number().int().nonnegative(), failed: z.number().int().nonnegative(),
|
|
425
|
+
revisions: z.array(z.looseObject({ specRevision: z.string(), executions: z.number().int().nonnegative() }))
|
|
426
|
+
}).describe('Earlier spec revisions: context only, counted nowhere above'),
|
|
427
|
+
excluded: z.record(z.string(), z.number().int().nonnegative()).describe('Executions in the scope that are not trials, by reason: `not-run`, `unfinished`, `unknown-status`, `historical-revision`, `revision-not-recorded`'),
|
|
428
|
+
outsideScope: z.number().int().nonnegative().describe('Executions the scope did not select'),
|
|
429
|
+
snapshots: z.number().int().nonnegative().describe('Raw recorded snapshots behind the counted executions'),
|
|
430
|
+
cohorts: z.array(ReliabilityRateSchema.extend({ environment: z.string().nullable(), producer: z.string(), parameters: z.record(z.string(), z.string()) })),
|
|
431
|
+
available: z.looseObject({
|
|
432
|
+
environments: z.array(FacetValueSchema), producers: z.array(FacetValueSchema), branches: z.array(FacetValueSchema), specRevisions: z.array(FacetValueSchema)
|
|
433
|
+
}).describe('What a scope could choose among, with independent executions'),
|
|
434
|
+
notes: z.array(z.looseObject({ code: z.string().describe('An open set: treat a code you do not know as informational'), severity: z.enum(['warning', 'info']), message: z.string() })),
|
|
435
|
+
executions: z.array(z.looseObject({
|
|
436
|
+
run: z.string(), at: z.number(), status: z.string(), counted: z.boolean(), excluded: z.string().optional(),
|
|
437
|
+
environment: z.string().nullable(), producer: z.string(), kind: z.string(), parameters: z.record(z.string(), z.string()),
|
|
438
|
+
branch: z.string().nullable(), sha: z.string().nullable(), runUrl: z.string().nullable(), layer: z.string(), backend: z.string(),
|
|
439
|
+
durationMs: z.number().nullable(), specRevision: z.string().optional(), snapshots: z.number().int().positive(),
|
|
440
|
+
attempts: z.array(z.string()).optional().describe('The status of each recorded try, in order'), firstAttempt: z.string().optional(),
|
|
441
|
+
republished: z.boolean().describe('A failed snapshot of this execution was followed by a later one')
|
|
442
|
+
})).describe('Every independent execution of the selection, newest first: the denominator, open to inspection')
|
|
443
|
+
});
|
|
392
444
|
/** A run: the layers that name the same execution (else share a run URL, else a branch and commit), with what they recorded. */
|
|
393
445
|
export const ReportRunSchema = z.looseObject({
|
|
394
446
|
key: z.string().describe('`run:<id>` for an execution a producer named, else the run URL, else `branch@sha`. Pass it to `/api/reports/runs/detail?key=` or `/api/reports/tests?run=`'),
|
|
@@ -74,16 +74,40 @@ export declare const LayerRunSchema: z.ZodObject<{
|
|
|
74
74
|
}, z.core.$loose>;
|
|
75
75
|
/** The execution of a layer, parsed from `LayerRunSchema`. */
|
|
76
76
|
export type LayerRun = z.output<typeof LayerRunSchema>;
|
|
77
|
+
/** The `kind` of a measurement that is a named, user-visible product latency (see docs/rich-evidence.md#product-latency). */
|
|
78
|
+
export declare const PRODUCT_LATENCY_KIND = "product-latency";
|
|
79
|
+
/** The two ends of a measured interval, named in the producer's words: `{ from: 'submit pressed', to: 'confirmation visible' }`. */
|
|
80
|
+
export declare const MeasurementBoundarySchema: z.ZodObject<{
|
|
81
|
+
from: z.ZodOptional<z.ZodString>;
|
|
82
|
+
to: z.ZodOptional<z.ZodString>;
|
|
83
|
+
}, z.core.$loose>;
|
|
77
84
|
/**
|
|
78
85
|
* A number a check measured, such as a latency or a size, with its unit and an optional allowance. A measurement over
|
|
79
86
|
* its `limit` is reported and never changes the check's status.
|
|
87
|
+
*
|
|
88
|
+
* A measurement of `kind` `product-latency` is how quickly the product responded to a user, between two named events.
|
|
89
|
+
* It is not the check's duration (which includes setup, waits and pacing) and a number with no `kind` is never read as
|
|
90
|
+
* product latency. All the fields after `limit` are optional and describe the measurement; none decides a status.
|
|
80
91
|
*/
|
|
81
92
|
export declare const MeasurementSchema: z.ZodObject<{
|
|
82
93
|
name: z.ZodString;
|
|
83
94
|
value: z.ZodNumber;
|
|
84
95
|
unit: z.ZodString;
|
|
85
96
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
97
|
+
kind: z.ZodOptional<z.ZodString>;
|
|
98
|
+
from: z.ZodOptional<z.ZodString>;
|
|
99
|
+
to: z.ZodOptional<z.ZodString>;
|
|
100
|
+
intended: z.ZodOptional<z.ZodObject<{
|
|
101
|
+
from: z.ZodOptional<z.ZodString>;
|
|
102
|
+
to: z.ZodOptional<z.ZodString>;
|
|
103
|
+
}, z.core.$loose>>;
|
|
104
|
+
source: z.ZodOptional<z.ZodString>;
|
|
105
|
+
outcome: z.ZodOptional<z.ZodString>;
|
|
106
|
+
instrumentation: z.ZodOptional<z.ZodString>;
|
|
107
|
+
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
86
108
|
}, z.core.$loose>;
|
|
109
|
+
/** A measurement as stored in a layer, parsed from `MeasurementSchema`. */
|
|
110
|
+
export type Measurement = z.output<typeof MeasurementSchema>;
|
|
87
111
|
/**
|
|
88
112
|
* Structured data of a producer's or tool's own, by namespace: `{ "com.example.coverage": { … } }`.
|
|
89
113
|
* Use a name you own (a reverse domain, or your tool's name). Readers preserve it and never fail on it.
|
|
@@ -867,6 +891,17 @@ export declare const CheckSchema: z.ZodObject<{
|
|
|
867
891
|
value: z.ZodNumber;
|
|
868
892
|
unit: z.ZodString;
|
|
869
893
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
894
|
+
kind: z.ZodOptional<z.ZodString>;
|
|
895
|
+
from: z.ZodOptional<z.ZodString>;
|
|
896
|
+
to: z.ZodOptional<z.ZodString>;
|
|
897
|
+
intended: z.ZodOptional<z.ZodObject<{
|
|
898
|
+
from: z.ZodOptional<z.ZodString>;
|
|
899
|
+
to: z.ZodOptional<z.ZodString>;
|
|
900
|
+
}, z.core.$loose>>;
|
|
901
|
+
source: z.ZodOptional<z.ZodString>;
|
|
902
|
+
outcome: z.ZodOptional<z.ZodString>;
|
|
903
|
+
instrumentation: z.ZodOptional<z.ZodString>;
|
|
904
|
+
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
870
905
|
}, z.core.$loose>>>;
|
|
871
906
|
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
872
907
|
recordingExpectation: z.ZodOptional<z.ZodString>;
|
|
@@ -1338,6 +1373,17 @@ export declare const ReportPartSchema: z.ZodObject<{
|
|
|
1338
1373
|
value: z.ZodNumber;
|
|
1339
1374
|
unit: z.ZodString;
|
|
1340
1375
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
1376
|
+
kind: z.ZodOptional<z.ZodString>;
|
|
1377
|
+
from: z.ZodOptional<z.ZodString>;
|
|
1378
|
+
to: z.ZodOptional<z.ZodString>;
|
|
1379
|
+
intended: z.ZodOptional<z.ZodObject<{
|
|
1380
|
+
from: z.ZodOptional<z.ZodString>;
|
|
1381
|
+
to: z.ZodOptional<z.ZodString>;
|
|
1382
|
+
}, z.core.$loose>>;
|
|
1383
|
+
source: z.ZodOptional<z.ZodString>;
|
|
1384
|
+
outcome: z.ZodOptional<z.ZodString>;
|
|
1385
|
+
instrumentation: z.ZodOptional<z.ZodString>;
|
|
1386
|
+
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
1341
1387
|
}, z.core.$loose>>>;
|
|
1342
1388
|
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
1343
1389
|
recordingExpectation: z.ZodOptional<z.ZodString>;
|
|
@@ -1708,6 +1754,17 @@ export declare const LayerSourceSchema: z.ZodObject<{
|
|
|
1708
1754
|
value: z.ZodNumber;
|
|
1709
1755
|
unit: z.ZodString;
|
|
1710
1756
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
1757
|
+
kind: z.ZodOptional<z.ZodString>;
|
|
1758
|
+
from: z.ZodOptional<z.ZodString>;
|
|
1759
|
+
to: z.ZodOptional<z.ZodString>;
|
|
1760
|
+
intended: z.ZodOptional<z.ZodObject<{
|
|
1761
|
+
from: z.ZodOptional<z.ZodString>;
|
|
1762
|
+
to: z.ZodOptional<z.ZodString>;
|
|
1763
|
+
}, z.core.$loose>>;
|
|
1764
|
+
source: z.ZodOptional<z.ZodString>;
|
|
1765
|
+
outcome: z.ZodOptional<z.ZodString>;
|
|
1766
|
+
instrumentation: z.ZodOptional<z.ZodString>;
|
|
1767
|
+
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
1711
1768
|
}, z.core.$loose>>>;
|
|
1712
1769
|
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
1713
1770
|
recordingExpectation: z.ZodOptional<z.ZodString>;
|
|
@@ -2324,6 +2381,17 @@ export declare const LayerDocumentSchema: z.ZodObject<{
|
|
|
2324
2381
|
value: z.ZodNumber;
|
|
2325
2382
|
unit: z.ZodString;
|
|
2326
2383
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
2384
|
+
kind: z.ZodOptional<z.ZodString>;
|
|
2385
|
+
from: z.ZodOptional<z.ZodString>;
|
|
2386
|
+
to: z.ZodOptional<z.ZodString>;
|
|
2387
|
+
intended: z.ZodOptional<z.ZodObject<{
|
|
2388
|
+
from: z.ZodOptional<z.ZodString>;
|
|
2389
|
+
to: z.ZodOptional<z.ZodString>;
|
|
2390
|
+
}, z.core.$loose>>;
|
|
2391
|
+
source: z.ZodOptional<z.ZodString>;
|
|
2392
|
+
outcome: z.ZodOptional<z.ZodString>;
|
|
2393
|
+
instrumentation: z.ZodOptional<z.ZodString>;
|
|
2394
|
+
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
2327
2395
|
}, z.core.$loose>>>;
|
|
2328
2396
|
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
2329
2397
|
recordingExpectation: z.ZodOptional<z.ZodString>;
|
|
@@ -2693,6 +2761,17 @@ export declare const LayerDocumentSchema: z.ZodObject<{
|
|
|
2693
2761
|
value: z.ZodNumber;
|
|
2694
2762
|
unit: z.ZodString;
|
|
2695
2763
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
2764
|
+
kind: z.ZodOptional<z.ZodString>;
|
|
2765
|
+
from: z.ZodOptional<z.ZodString>;
|
|
2766
|
+
to: z.ZodOptional<z.ZodString>;
|
|
2767
|
+
intended: z.ZodOptional<z.ZodObject<{
|
|
2768
|
+
from: z.ZodOptional<z.ZodString>;
|
|
2769
|
+
to: z.ZodOptional<z.ZodString>;
|
|
2770
|
+
}, z.core.$loose>>;
|
|
2771
|
+
source: z.ZodOptional<z.ZodString>;
|
|
2772
|
+
outcome: z.ZodOptional<z.ZodString>;
|
|
2773
|
+
instrumentation: z.ZodOptional<z.ZodString>;
|
|
2774
|
+
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
2696
2775
|
}, z.core.$loose>>>;
|
|
2697
2776
|
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
2698
2777
|
recordingExpectation: z.ZodOptional<z.ZodString>;
|
|
@@ -2901,6 +2980,17 @@ export declare const PublishChecksSchema: z.ZodObject<{
|
|
|
2901
2980
|
value: z.ZodNumber;
|
|
2902
2981
|
unit: z.ZodString;
|
|
2903
2982
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
2983
|
+
kind: z.ZodOptional<z.ZodString>;
|
|
2984
|
+
from: z.ZodOptional<z.ZodString>;
|
|
2985
|
+
to: z.ZodOptional<z.ZodString>;
|
|
2986
|
+
intended: z.ZodOptional<z.ZodObject<{
|
|
2987
|
+
from: z.ZodOptional<z.ZodString>;
|
|
2988
|
+
to: z.ZodOptional<z.ZodString>;
|
|
2989
|
+
}, z.core.$loose>>;
|
|
2990
|
+
source: z.ZodOptional<z.ZodString>;
|
|
2991
|
+
outcome: z.ZodOptional<z.ZodString>;
|
|
2992
|
+
instrumentation: z.ZodOptional<z.ZodString>;
|
|
2993
|
+
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
2904
2994
|
}, z.core.$loose>>>;
|
|
2905
2995
|
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
2906
2996
|
recordingExpectation: z.ZodOptional<z.ZodString>;
|
package/dist/schema/src/layer.js
CHANGED
|
@@ -62,15 +62,34 @@ export const LayerRunSchema = z.looseObject({
|
|
|
62
62
|
startedAtMs: z.number().nonnegative().optional().describe('When the execution started, epoch milliseconds'),
|
|
63
63
|
endedAtMs: z.number().nonnegative().optional().describe('When the execution ended, epoch milliseconds')
|
|
64
64
|
});
|
|
65
|
+
/** The `kind` of a measurement that is a named, user-visible product latency (see docs/rich-evidence.md#product-latency). */
|
|
66
|
+
export const PRODUCT_LATENCY_KIND = 'product-latency';
|
|
67
|
+
/** The two ends of a measured interval, named in the producer's words: `{ from: 'submit pressed', to: 'confirmation visible' }`. */
|
|
68
|
+
export const MeasurementBoundarySchema = z.looseObject({
|
|
69
|
+
from: z.string().min(1).optional().describe('The event the interval starts at: `end of speech`, `submit pressed`'),
|
|
70
|
+
to: z.string().min(1).optional().describe('The event the interval ends at: `audible response`, `confirmation visible`')
|
|
71
|
+
});
|
|
65
72
|
/**
|
|
66
73
|
* A number a check measured, such as a latency or a size, with its unit and an optional allowance. A measurement over
|
|
67
74
|
* its `limit` is reported and never changes the check's status.
|
|
75
|
+
*
|
|
76
|
+
* A measurement of `kind` `product-latency` is how quickly the product responded to a user, between two named events.
|
|
77
|
+
* It is not the check's duration (which includes setup, waits and pacing) and a number with no `kind` is never read as
|
|
78
|
+
* product latency. All the fields after `limit` are optional and describe the measurement; none decides a status.
|
|
68
79
|
*/
|
|
69
80
|
export const MeasurementSchema = z.looseObject({
|
|
70
81
|
name: z.string().min(1),
|
|
71
82
|
value: z.number().describe('A finite number'),
|
|
72
83
|
unit: z.string(),
|
|
73
|
-
limit: z.number().optional().describe('The allowance. A measurement over it is reported, and never fails the check by itself')
|
|
84
|
+
limit: z.number().optional().describe('The allowance, or budget. A measurement over it is reported, and never fails the check by itself'),
|
|
85
|
+
kind: z.string().min(1).optional().describe('What the number is, in an open vocabulary. `product-latency`: a user-visible response time between `from` and `to`. Absent: just a number, never read as product latency'),
|
|
86
|
+
from: z.string().min(1).optional().describe('The event the interval starts at. Without both `from` and `to` a latency is shown but no product claim is made from it'),
|
|
87
|
+
to: z.string().min(1).optional().describe('The event the interval ends at'),
|
|
88
|
+
intended: MeasurementBoundarySchema.optional().describe('The interval the name of the measurement claims, when what was measured is only part of it. If `intended.to` differs from `to` (or `intended.from` from `from`) the measurement is shown as partial, never as the complete interval'),
|
|
89
|
+
source: z.string().optional().describe('How it was measured: `app-instrumentation`, `trace`, `recording-analysis`'),
|
|
90
|
+
outcome: z.string().min(1).optional().describe('`observed` (the default), `timeout` or `failed`. For anything but `observed` the value is how long the observation waited: at least that much, so it counts against a budget and is never dropped'),
|
|
91
|
+
instrumentation: z.string().optional().describe('The version or method of the measurement. A change of it between two sets of observations is disclosed when they are compared'),
|
|
92
|
+
labels: z.record(z.string(), z.string()).optional().describe('What the observation is comparable with: `device`, `build`, `network`. `device` and `build` group the observations and, when two groups differ in the others, the comparison says so')
|
|
74
93
|
});
|
|
75
94
|
/**
|
|
76
95
|
* Structured data of a producer's or tool's own, by namespace: `{ "com.example.coverage": { … } }`.
|
package/dist/server/src/app.js
CHANGED
|
@@ -28,6 +28,7 @@ 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
30
|
import { parseArtifactFilter } from '../../reports/src/artifact-query.js';
|
|
31
|
+
import { parseReliabilityScope, reliability, reliabilityMarkdown } from '../../reports/src/reliability.js';
|
|
31
32
|
import { artifactList, artifactsMarkdown, catalogMarkdown, catalogModel, historyMarkdown, parseTestQuery, runDetail, runList, runMarkdown, runsMarkdown, testCatalog, testHistory } from '../../reports/src/test-catalog.js';
|
|
32
33
|
import { llmsTxt } from './llms.js';
|
|
33
34
|
import { openApiDocument } from './openapi.js';
|
|
@@ -467,6 +468,20 @@ function createProjectApplication(options) {
|
|
|
467
468
|
return problemResponse(404, 'No test in the report has that key.');
|
|
468
469
|
return answer(c, found.version, () => document, () => historyMarkdown(document), `history|${key}|${wantsMarkdown(c)}`);
|
|
469
470
|
});
|
|
471
|
+
app.get('/api/reports/tests/reliability', async (c) => {
|
|
472
|
+
if (!index.ready)
|
|
473
|
+
return notReady(c);
|
|
474
|
+
const key = c.req.query('key');
|
|
475
|
+
if (!key)
|
|
476
|
+
return problemResponse(400, 'Name the test with ?key=, a key from GET /api/reports/tests.');
|
|
477
|
+
const found = await catalog();
|
|
478
|
+
const recorded = testHistory(found, key);
|
|
479
|
+
if (!recorded)
|
|
480
|
+
return problemResponse(404, 'No test in the report has that key.');
|
|
481
|
+
const search = new URL(c.req.url).searchParams;
|
|
482
|
+
const document = () => reliability(key, recorded.history, parseReliabilityScope(search));
|
|
483
|
+
return answer(c, found.version, document, () => reliabilityMarkdown(document()), `reliability|${search.toString()}|${wantsMarkdown(c)}`);
|
|
484
|
+
});
|
|
470
485
|
app.get('/api/reports/runs', async (c) => {
|
|
471
486
|
if (!index.ready)
|
|
472
487
|
return notReady(c);
|
package/dist/server/src/llms.js
CHANGED
|
@@ -68,6 +68,7 @@ Every endpoint below answers JSON, or Markdown with \`format=markdown\`. Prefix
|
|
|
68
68
|
curl "${url}/api/projects/$ATLAS_PROJECT/reports/tests?format=markdown&limit=50" # summary, environments, domains, failing, flaky, slowest, then the tests
|
|
69
69
|
curl "${url}/api/projects/$ATLAS_PROJECT/reports/tests?status=failed,flaky&environment=desktop-web" # filter: q, status, environment, producer, domain, spec, tag, run; sort, offset, limit
|
|
70
70
|
curl "${url}/api/projects/$ATLAS_PROJECT/reports/tests/history?key=<url-encoded test key>" # one test: every recorded result, durations, branches, CI runs
|
|
71
|
+
curl "${url}/api/projects/$ATLAS_PROJECT/reports/tests/reliability?key=<url-encoded test key>&environment=phone&format=markdown" # how reliable one test is in a scope: independent executions, first-try vs eventual success, what was excluded and why
|
|
71
72
|
curl "${url}/api/projects/$ATLAS_PROJECT/reports/tests/health?format=markdown" # why Specs, domains and counts disagree; add root=<from>><to> to declare a path-root mapping
|
|
72
73
|
curl "${url}/api/projects/$ATLAS_PROJECT/reports/runs?format=markdown" # CI runs, newest first, with their results by status and video counts
|
|
73
74
|
curl "${url}/api/projects/$ATLAS_PROJECT/reports/runs?video=true&format=markdown" # only runs with a playable video recording
|
|
@@ -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, ArtifactListSchema, 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, TestReliabilitySchema, 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'], ArtifactList: [ArtifactListSchema, 'output'], RunDetail: [RunDetailSchema, 'output'],
|
|
40
|
+
TestCatalog: [TestCatalogSchema, 'output'], CatalogHealth: [CatalogHealthSchema, 'output'], TestHistory: [TestHistorySchema, 'output'], TestReliability: [TestReliabilitySchema, '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
|
};
|
|
@@ -328,6 +328,29 @@ const BASE_OPERATIONS = [
|
|
|
328
328
|
],
|
|
329
329
|
responses: { '200': json('The test and its history.', 'TestHistory'), '304': { description: 'Unchanged since the `If-None-Match` ETag.' }, '400': problem('No `key`.'), '401': UNAUTHORIZED, '404': problem('No test in the report has that key.'), '503': NOT_READY }
|
|
330
330
|
},
|
|
331
|
+
{
|
|
332
|
+
route: 'GET /api/reports/tests/reliability', path: '/api/reports/tests/reliability', operationId: 'getTestReliability', tag: 'Report', role: 'read',
|
|
333
|
+
summary: 'How reliable one test is in a scope, and how that was counted',
|
|
334
|
+
description: [
|
|
335
|
+
'A success rate for one test that says what it counted. The snapshots one run recorded for one producer, environment and parameter set (running, finished, enriched) are one independent execution; its last word counts once with the tries it recorded. `eventual` is the share of executions that passed in the end, `firstAttempt` the share whose first try passed (over executions that recorded their tries; the rest are `unrecordedAttempts`, never assumed to have passed first time).',
|
|
336
|
+
'',
|
|
337
|
+
'Choose the scope with `environment`, `producer`, `parameter` (`key=value`), `branch`, `sha`, `specRevision`, `since` and `until`; executions elsewhere never count. By default only the newest recorded spec revision counts and earlier ones appear under `historical`; send `revision=any` to pool them. Only `passed` and `failed` executions are trials: `not-run` and unfinished ones are listed under `excluded` and never counted as a pass. `sample` is `none`, `insufficient` (fewer than `minimumExecutions`) or `sufficient`, a count only: `notes` say when environments, producers or branches are pooled, failed cases were published again, or history is bounded to the newest 100 results. `executions` is the denominator, open to inspection. The interval states its assumptions. This is not a production failure probability. Send `format=markdown` for Markdown.'
|
|
338
|
+
].join('\n'),
|
|
339
|
+
parameters: [
|
|
340
|
+
{ name: 'key', in: 'query', required: true, description: 'The test\'s `key` from `/api/reports/tests`.', schema: str, example: '["specs/checkout/pay.feature","Pay with a card"]' },
|
|
341
|
+
{ name: 'environment', in: 'query', description: 'Environments or devices to count. Comma-separated.', schema: str, example: 'phone' },
|
|
342
|
+
{ name: 'producer', in: 'query', description: 'Producers to count. Comma-separated.', schema: str },
|
|
343
|
+
{ name: 'parameter', in: 'query', description: 'Parameter choices `key=value`; all must match. Comma-separated.', schema: str, example: 'device=small' },
|
|
344
|
+
{ name: 'branch', in: 'query', description: 'Branches to count. Comma-separated.', schema: str },
|
|
345
|
+
{ name: 'sha', in: 'query', description: 'Commits: a full id or a prefix of at least seven characters. Comma-separated.', schema: str },
|
|
346
|
+
{ name: 'specRevision', in: 'query', description: 'Only executions recorded against these spec fingerprints (overrides `revision`). Comma-separated.', schema: str },
|
|
347
|
+
{ name: 'revision', in: 'query', description: '`any` pools every spec revision; otherwise only the newest recorded one counts.', schema: { type: 'string', enum: ['current', 'any'] } },
|
|
348
|
+
{ name: 'since', in: 'query', description: 'Only executions at or after this time: epoch milliseconds or a date.', schema: str, example: '2026-01-01' },
|
|
349
|
+
{ name: 'until', in: 'query', description: 'Only executions at or before this time: epoch milliseconds or a date.', schema: str },
|
|
350
|
+
{ name: 'format', in: 'query', description: '`markdown` for Markdown instead of JSON.', schema: { type: 'string', enum: ['json', 'markdown'] } }
|
|
351
|
+
],
|
|
352
|
+
responses: { '200': json('The reliability of the test in the scope.', 'TestReliability'), '304': { description: 'Unchanged since the `If-None-Match` ETag.' }, '400': problem('No `key`.'), '401': UNAUTHORIZED, '404': problem('No test in the report has that key.'), '503': NOT_READY }
|
|
353
|
+
},
|
|
331
354
|
{
|
|
332
355
|
route: 'GET /api/reports/artifact-tags', path: '/api/reports/artifact-tags', operationId: 'getArtifactTags', tag: 'Report', role: 'read',
|
|
333
356
|
summary: 'Reviewer tags of attachments: every recorded edit',
|
package/docs/extending.md
CHANGED
|
@@ -34,7 +34,7 @@ carry any of these. Every one is optional.
|
|
|
34
34
|
| `scope{}` | What the evidence does and does not show: surface, device kind, synthetic data, scripted dependencies, audio, pacing, what was asserted and its limits. Unknown when absent. See [Evidence scope](rich-evidence.md#evidence-scope). |
|
|
35
35
|
| `links[]` | Where to read more: the CI run, the issue, the trace client. |
|
|
36
36
|
| `extensions{}` | Structured data of your own or a tool's, under a name you own. |
|
|
37
|
-
| `measurements[]` | Numbers, with units and an allowance (already part of the contract). |
|
|
37
|
+
| `measurements[]` | Numbers, with units and an allowance (already part of the contract). A measurement of `kind` `product-latency` names a user-visible response time between two events; see [Product latency](rich-evidence.md#product-latency). |
|
|
38
38
|
|
|
39
39
|
An attachment may carry its own `tags[]` (`demo`): shown as `#demo` and searchable; see
|
|
40
40
|
[Tagging artifacts and demos](rich-evidence.md#tagging-artifacts-and-demos).
|
|
@@ -598,6 +598,54 @@ Scoped to one project. The same project may be selected on the unprefixed API us
|
|
|
598
598
|
|
|
599
599
|
Responses: `200` The test and its history.; `304` Unchanged since the `If-None-Match` ETag.; `400` No `key`.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `404` No test in the report has that key.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
|
|
600
600
|
|
|
601
|
+
### How reliable one test is in a scope, and how that was counted
|
|
602
|
+
|
|
603
|
+
`GET /api/reports/tests/reliability` · role: read
|
|
604
|
+
|
|
605
|
+
A success rate for one test that says what it counted. The snapshots one run recorded for one producer, environment and parameter set (running, finished, enriched) are one independent execution; its last word counts once with the tries it recorded. `eventual` is the share of executions that passed in the end, `firstAttempt` the share whose first try passed (over executions that recorded their tries; the rest are `unrecordedAttempts`, never assumed to have passed first time).
|
|
606
|
+
|
|
607
|
+
Choose the scope with `environment`, `producer`, `parameter` (`key=value`), `branch`, `sha`, `specRevision`, `since` and `until`; executions elsewhere never count. By default only the newest recorded spec revision counts and earlier ones appear under `historical`; send `revision=any` to pool them. Only `passed` and `failed` executions are trials: `not-run` and unfinished ones are listed under `excluded` and never counted as a pass. `sample` is `none`, `insufficient` (fewer than `minimumExecutions`) or `sufficient`, a count only: `notes` say when environments, producers or branches are pooled, failed cases were published again, or history is bounded to the newest 100 results. `executions` is the denominator, open to inspection. The interval states its assumptions. This is not a production failure probability. Send `format=markdown` for Markdown.
|
|
608
|
+
|
|
609
|
+
- `key` (query, required): The test's `key` from `/api/reports/tests`.
|
|
610
|
+
- `environment` (query): Environments or devices to count. Comma-separated.
|
|
611
|
+
- `producer` (query): Producers to count. Comma-separated.
|
|
612
|
+
- `parameter` (query): Parameter choices `key=value`; all must match. Comma-separated.
|
|
613
|
+
- `branch` (query): Branches to count. Comma-separated.
|
|
614
|
+
- `sha` (query): Commits: a full id or a prefix of at least seven characters. Comma-separated.
|
|
615
|
+
- `specRevision` (query): Only executions recorded against these spec fingerprints (overrides `revision`). Comma-separated.
|
|
616
|
+
- `revision` (query): `any` pools every spec revision; otherwise only the newest recorded one counts.
|
|
617
|
+
- `since` (query): Only executions at or after this time: epoch milliseconds or a date.
|
|
618
|
+
- `until` (query): Only executions at or before this time: epoch milliseconds or a date.
|
|
619
|
+
- `format` (query): `markdown` for Markdown instead of JSON.
|
|
620
|
+
- `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
|
|
621
|
+
|
|
622
|
+
Responses: `200` The reliability of the test in the scope.; `304` Unchanged since the `If-None-Match` ETag.; `400` No `key`.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `404` No test in the report has that key.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
|
|
623
|
+
|
|
624
|
+
### How reliable one test is in a scope, and how that was counted
|
|
625
|
+
|
|
626
|
+
`GET /api/projects/{project}/reports/tests/reliability` · role: read
|
|
627
|
+
|
|
628
|
+
A success rate for one test that says what it counted. The snapshots one run recorded for one producer, environment and parameter set (running, finished, enriched) are one independent execution; its last word counts once with the tries it recorded. `eventual` is the share of executions that passed in the end, `firstAttempt` the share whose first try passed (over executions that recorded their tries; the rest are `unrecordedAttempts`, never assumed to have passed first time).
|
|
629
|
+
|
|
630
|
+
Choose the scope with `environment`, `producer`, `parameter` (`key=value`), `branch`, `sha`, `specRevision`, `since` and `until`; executions elsewhere never count. By default only the newest recorded spec revision counts and earlier ones appear under `historical`; send `revision=any` to pool them. Only `passed` and `failed` executions are trials: `not-run` and unfinished ones are listed under `excluded` and never counted as a pass. `sample` is `none`, `insufficient` (fewer than `minimumExecutions`) or `sufficient`, a count only: `notes` say when environments, producers or branches are pooled, failed cases were published again, or history is bounded to the newest 100 results. `executions` is the denominator, open to inspection. The interval states its assumptions. This is not a production failure probability. Send `format=markdown` for Markdown.
|
|
631
|
+
|
|
632
|
+
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.
|
|
633
|
+
|
|
634
|
+
- `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.
|
|
635
|
+
- `key` (query, required): The test's `key` from `/api/reports/tests`.
|
|
636
|
+
- `environment` (query): Environments or devices to count. Comma-separated.
|
|
637
|
+
- `producer` (query): Producers to count. Comma-separated.
|
|
638
|
+
- `parameter` (query): Parameter choices `key=value`; all must match. Comma-separated.
|
|
639
|
+
- `branch` (query): Branches to count. Comma-separated.
|
|
640
|
+
- `sha` (query): Commits: a full id or a prefix of at least seven characters. Comma-separated.
|
|
641
|
+
- `specRevision` (query): Only executions recorded against these spec fingerprints (overrides `revision`). Comma-separated.
|
|
642
|
+
- `revision` (query): `any` pools every spec revision; otherwise only the newest recorded one counts.
|
|
643
|
+
- `since` (query): Only executions at or after this time: epoch milliseconds or a date.
|
|
644
|
+
- `until` (query): Only executions at or before this time: epoch milliseconds or a date.
|
|
645
|
+
- `format` (query): `markdown` for Markdown instead of JSON.
|
|
646
|
+
|
|
647
|
+
Responses: `200` The reliability of the test in the scope.; `304` Unchanged since the `If-None-Match` ETag.; `400` No `key`.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `404` No test in the report has that key.; `503` The server is still reading its backends. Retry after the number of seconds in `Retry-After`.
|
|
648
|
+
|
|
601
649
|
### Reviewer tags of attachments: every recorded edit
|
|
602
650
|
|
|
603
651
|
`GET /api/reports/artifact-tags` · role: read
|