@crvouga/atlas 1.11.0 → 1.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.
- package/CHANGELOG.md +13 -1
- package/dist/critics/src/contract.d.ts +113 -0
- package/dist/critics/src/contract.js +48 -0
- package/dist/critics/src/credentials.d.ts +72 -0
- package/dist/critics/src/credentials.js +136 -0
- package/dist/critics/src/critique.d.ts +128 -0
- package/dist/critics/src/critique.js +163 -0
- package/dist/critics/src/index.d.ts +21 -0
- package/dist/critics/src/index.js +21 -0
- package/dist/critics/src/judging.d.ts +153 -0
- package/dist/critics/src/judging.js +145 -0
- package/dist/critics/src/publish.d.ts +23 -0
- package/dist/critics/src/publish.js +21 -0
- package/dist/critics/src/runner.d.ts +104 -0
- package/dist/critics/src/runner.js +202 -0
- package/dist/critics/src/scope.d.ts +8 -0
- package/dist/critics/src/scope.js +26 -0
- package/dist/reports/src/compose.d.ts +2 -2
- package/dist/reports/src/compose.js +6 -5
- package/dist/reports/src/evidence-view.d.ts +2 -1
- package/dist/reports/src/evidence-view.js +5 -2
- package/dist/reports/src/layer-merge.d.ts +11 -3
- package/dist/reports/src/layer-merge.js +14 -3
- package/dist/reports/src/report-status.d.ts +1 -1
- package/dist/reports/src/report-status.js +6 -4
- package/dist/schema/src/api.d.ts +1 -0
- package/dist/schema/src/api.js +1 -1
- package/dist/schema/src/critique.d.ts +144 -0
- package/dist/schema/src/critique.js +96 -0
- package/dist/schema/src/index.d.ts +2 -1
- package/dist/schema/src/index.js +2 -1
- package/dist/schema/src/server-config.d.ts +7 -1
- package/dist/schema/src/server-config.js +6 -3
- package/dist/server/src/auth.d.ts +1 -1
- package/dist/server/src/auth.js +2 -2
- package/dist/server/src/config.js +2 -2
- package/dist/server/src/openapi.d.ts +3 -3
- package/dist/server/src/openapi.js +7 -4
- package/dist/server/src/store-api.js +39 -1
- package/docs/agents.md +1 -0
- package/docs/critics.md +190 -0
- package/docs/extending.md +8 -0
- package/docs/reference/critics.md +840 -0
- package/docs/reference/http-api.md +6 -2
- package/docs/reference/reports.md +20 -7
- package/docs/reference/schema.md +119 -2
- package/docs/reference/server.md +3 -3
- package/docs/report-contract.md +4 -0
- package/docs/rich-evidence.md +39 -0
- package/docs/server.md +6 -4
- package/docs/ui.md +22 -0
- package/llms-full.txt +279 -6
- package/llms.txt +2 -0
- package/package.json +7 -2
- package/packages/critics/README.md +18 -0
- package/packages/schema/schemas/critique.schema.json +254 -0
- package/packages/schema/schemas/server-config.schema.json +2 -1
- package/packages/schema/schemas/server-info.schema.json +1 -0
- package/packages/server/openapi.json +7 -6
- package/packages/visualizer/src/app/router.tsx +3 -0
- package/packages/visualizer/src/evidence/artifacts.tsx +88 -42
- package/packages/visualizer/src/evidence/behavior-panel.tsx +44 -25
- package/packages/visualizer/src/evidence/media-timing.test.ts +85 -0
- package/packages/visualizer/src/evidence/media-timing.ts +150 -0
- package/packages/visualizer/src/evidence/moments.tsx +457 -0
- package/packages/visualizer/src/evidence/outcome-summary.tsx +27 -0
- package/packages/visualizer/src/evidence/outcome.ts +7 -2
- package/packages/visualizer/src/explorer/TestDetail.tsx +2 -2
- package/packages/visualizer/src/explorer/playlist.tsx +89 -21
- package/packages/visualizer/src/explorer/search.ts +7 -1
package/llms-full.txt
CHANGED
|
@@ -112,6 +112,7 @@ The complete public export map is generated from the package manifest (wildcards
|
|
|
112
112
|
@crvouga/atlas/vitest
|
|
113
113
|
@crvouga/atlas/schema
|
|
114
114
|
@crvouga/atlas/server
|
|
115
|
+
@crvouga/atlas/critics
|
|
115
116
|
@crvouga/atlas/reports/*
|
|
116
117
|
@crvouga/atlas/schemas/*
|
|
117
118
|
@crvouga/atlas/openapi.json
|
|
@@ -1014,6 +1015,7 @@ decided exactly as before ([the report contract](report-contract.md)). Evidence
|
|
|
1014
1015
|
| `attempts` | check | Every try of a retried check, with its own status, detail, steps and attachments. A check that passed after failing is flaky. |
|
|
1015
1016
|
| `tags`, `labels` | check, layer | Filters and groups (`smoke`, `flaky`, an owner, a browser). |
|
|
1016
1017
|
| `tags` | attachment | Marks one recording or screenshot, for example `demo`: shown as a `#demo` chip, and searchable. See [Tagging artifacts and demos](#tagging-artifacts-and-demos). |
|
|
1018
|
+
| `extensions['atlas.media']` | attachment (video) | Where the check's steps are in the recording, so a click on a step seeks the video. See [Place steps in a recording](#place-steps-in-a-recording). |
|
|
1017
1019
|
| `scope` | attachment, attempt, check, part, layer | What a recording does and does not show: where it ran, whether data and dependencies were real, whether audio was captured. See [Evidence scope](#evidence-scope). |
|
|
1018
1020
|
| `annotations`, `links` | check, step, attempt, layer | Notes, warnings and issue links next to the result. |
|
|
1019
1021
|
| `specPath` | check | The spec a check belongs to (a `.feature`, a test file), relative to the repository. Checks are grouped by it. |
|
|
@@ -1096,6 +1098,44 @@ Every field is optional, and every value except the booleans is an open string.
|
|
|
1096
1098
|
- **It decides nothing.** A scope never changes a status, a slot or a winner. A scope an older Atlas cannot read is
|
|
1097
1099
|
dropped, and the attachment, check and layer stay.
|
|
1098
1100
|
|
|
1101
|
+
## Place steps in a recording
|
|
1102
|
+
|
|
1103
|
+
A step says when it started on the check's clock (`offsetMs`, after the check started). A video has its own clock, which
|
|
1104
|
+
began when the recorder did, and the two are not assumed to agree: a recorder usually starts before the first step, and a
|
|
1105
|
+
paced demo may have delays put into the video that the check never waited for. To let a reviewer click a step and land on
|
|
1106
|
+
its moment in the video, say how the clocks relate in the `atlas.media` extension of the video attachment:
|
|
1107
|
+
|
|
1108
|
+
```ts
|
|
1109
|
+
{
|
|
1110
|
+
name: 'checkout.webm', role: 'video', durationMs: 9000,
|
|
1111
|
+
extensions: {
|
|
1112
|
+
'atlas.media': {
|
|
1113
|
+
checkStartMs: 1200, // the check started 1.2 s into the video
|
|
1114
|
+
pauses: [{ atMs: 900, durationMs: 2000, reason: 'title card' }], // 2 s put into the video before the step at +900 ms
|
|
1115
|
+
marks: [{ step: 'pay', mediaMs: 4100 }] // a step whose moment in the video you know exactly
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
Every field is optional and none of it decides a status, a slot or a winner. A step is placed at `checkStartMs` + its
|
|
1122
|
+
`offsetMs` + the `durationMs` of every pause whose `atMs` is at or before the step; a mark names the moment outright and
|
|
1123
|
+
wins. Anything else is unavailable and said so, never guessed.
|
|
1124
|
+
|
|
1125
|
+
- **Without timing, nothing is placed.** A video with no `atlas.media`, no `checkStartMs`, or a step with no `offsetMs` and
|
|
1126
|
+
no mark, is never assumed to start with the check, and a step is never placed from the wording of its name. Selecting
|
|
1127
|
+
such a step says why it cannot be shown, and moves nothing.
|
|
1128
|
+
- **Outside the video is not clamped.** A moment before the start, or after `durationMs` (or the length the player reports),
|
|
1129
|
+
is unavailable. A declaration with a field Atlas cannot read (a pause without a duration) is reported and places nothing.
|
|
1130
|
+
- **Pacing is not latency.** A video with `pauses`, or with a `scope` of `pacing: 'paced'`, is labelled as paced: the time
|
|
1131
|
+
between its steps includes deliberate pauses and is not application latency, and no measurement is changed by it.
|
|
1132
|
+
- **Each try has its own clock.** Put the video and the `atlas.media` of a retry on its `attempts[]` entry, with that
|
|
1133
|
+
try's `steps`. A try is aligned only by its own video, never by the check's or an earlier try's.
|
|
1134
|
+
- **Link to a moment.** The viewer copies a link that names the run (`layer`), the exact recording (`clip`, its digest or
|
|
1135
|
+
stored key) and the position (`clipAt`, milliseconds into the video). Opening it selects that recording of that run, even
|
|
1136
|
+
when a newer run exists, and goes to the position; a recording the run does not hold is reported, never replaced.
|
|
1137
|
+
- **Older layers.** A layer without `atlas.media` reads exactly as before; the video plays and its steps list as ever.
|
|
1138
|
+
|
|
1099
1139
|
## Product latency
|
|
1100
1140
|
|
|
1101
1141
|
A test's `durationMs` includes fixture setup, deliberate waits, demo pacing, retries and cleanup, so it says how long the
|
|
@@ -1632,7 +1672,7 @@ is said in words and the server does not start. The file has a published
|
|
|
1632
1672
|
| How often (seconds) the backends are looked at for layers written by something else | `refreshSeconds` | `--refresh <seconds>` | `ATLAS_REFRESH_SECONDS` | `10` |
|
|
1633
1673
|
| Let anyone look, not upload | `auth.anonymousRead` | `--anonymous-read` | `ATLAS_ANONYMOUS_READ` | no |
|
|
1634
1674
|
| Listen publicly with no tokens (something in front signs people in) | `auth.insecure` | `--insecure` | `ATLAS_INSECURE` | no |
|
|
1635
|
-
| The admin token (also `ATLAS_PUBLISH_TOKEN` and `ATLAS_READ_TOKEN` for the other roles, or `auth.tokens` in the file) | `auth.tokens` | | `ATLAS_TOKEN` | none: the server is open and listens on this machine only |
|
|
1675
|
+
| The admin token (also `ATLAS_PUBLISH_TOKEN`, `ATLAS_CRITIC_TOKEN` and `ATLAS_READ_TOKEN` for the other roles, or `auth.tokens` in the file) | `auth.tokens` | | `ATLAS_TOKEN` | none: the server is open and listens on this machine only |
|
|
1636
1676
|
| The configuration file | `(the file itself)` | `--config <file>` | `ATLAS_CONFIG` | `./atlas.server.json` when it exists |
|
|
1637
1677
|
| Largest uploaded object in MiB | `limits.maxObjectMiB` | | `ATLAS_MAX_OBJECT_MIB` | `256` |
|
|
1638
1678
|
| Largest JSON upload in MiB | `limits.maxJsonMiB` | | `ATLAS_MAX_JSON_MIB` | `32` |
|
|
@@ -1665,16 +1705,18 @@ backend in the client and `/readyz`, and recovers by itself.
|
|
|
1665
1705
|
| Role | May |
|
|
1666
1706
|
| --- | --- |
|
|
1667
1707
|
| `read` | look: the client, the report, `/api/status` |
|
|
1708
|
+
| `critic` | also publish [critiques](critics.md#authentication-and-authorization) and nothing else: blobs and layers whose producer is `critic.<name>` and whose parts are all of kind `critique`. It cannot post test results, start live runs or change settings |
|
|
1668
1709
|
| `publish` | also upload layers, specs and runs, and remove the scratch objects `atlas report doctor` writes |
|
|
1669
1710
|
| `admin` | all server operations |
|
|
1670
1711
|
|
|
1671
1712
|
Tokens are at least 12 characters (`openssl rand -hex 24`) and different from one another. Give them as
|
|
1672
|
-
`ATLAS_TOKEN` (admin), `ATLAS_PUBLISH_TOKEN`, `ATLAS_READ_TOKEN`, or under `auth.tokens` in the file, preferably
|
|
1713
|
+
`ATLAS_TOKEN` (admin), `ATLAS_PUBLISH_TOKEN`, `ATLAS_CRITIC_TOKEN`, `ATLAS_READ_TOKEN`, or under `auth.tokens` in the file, preferably
|
|
1673
1714
|
with `tokenEnv` so the file holds no secret.
|
|
1674
1715
|
|
|
1675
1716
|
- **Sending a token:** `Authorization: Bearer <token>`. A browser's sign-in box also works for *looking* (any
|
|
1676
1717
|
user name, the token as the password). **Uploading needs the Bearer header, never the sign-in box**: a browser
|
|
1677
1718
|
sends those credentials by itself to any page that asks, which would let another site upload with them.
|
|
1719
|
+
- **Run critics against a server with tokens.** With no tokens every caller is an administrator, a critic included.
|
|
1678
1720
|
- **No tokens** means open, and then the server refuses to listen beyond this machine unless told
|
|
1679
1721
|
`--insecure` (for when a proxy in front does the signing in).
|
|
1680
1722
|
- `/healthz` and `/readyz` never need a token, so a load balancer can use them.
|
|
@@ -1917,8 +1959,8 @@ curl "$ATLAS_SERVER_URL/api/projects/$ATLAS_PROJECT/reports/tests?format=markdow
|
|
|
1917
1959
|
| `GET /api/projects/{project}/store/{key}` | read | Read an object. |
|
|
1918
1960
|
| `HEAD /api/store/{key}` | read | Does an object exist. |
|
|
1919
1961
|
| `HEAD /api/projects/{project}/store/{key}` | read | Does an object exist. |
|
|
1920
|
-
| `PUT /api/store/{key}` |
|
|
1921
|
-
| `PUT /api/projects/{project}/store/{key}` |
|
|
1962
|
+
| `PUT /api/store/{key}` | critic | Upload an object. |
|
|
1963
|
+
| `PUT /api/projects/{project}/store/{key}` | critic | Upload an object. |
|
|
1922
1964
|
| `DELETE /api/store/{key}` | publish | Remove a scratch object. |
|
|
1923
1965
|
| `DELETE /api/projects/{project}/store/{key}` | publish | Remove a scratch object. |
|
|
1924
1966
|
| `GET /api/reports/tests` | read | Every test, with insights, facets and history statistics. |
|
|
@@ -2759,6 +2801,10 @@ An Atlas path that passed on retry (`flaky`) is `passed`, with the detail "Passe
|
|
|
2759
2801
|
Timings that only qualify a check (parts of kind `performance`) are not counted as checks when
|
|
2760
2802
|
anything else is recorded. A measurement over its allowance is reported and never fails a check.
|
|
2761
2803
|
|
|
2804
|
+
Opinions (parts of kind `critique`, the verdicts of [automated UI reviewers](critics.md)) are never counted as checks,
|
|
2805
|
+
even when they are all that was recorded, and are never part of the gate: a low score, or a critic that crashed, leaves
|
|
2806
|
+
`atlas report status` as it was. The `status` of a critique check says only whether the critic ran.
|
|
2807
|
+
|
|
2762
2808
|
**Freshness never changes a status.** A layer whose spec fingerprint no longer matches, whose
|
|
2763
2809
|
checkout was modified, or whose related code changed still shows its recorded outcome, with a
|
|
2764
2810
|
warning ("Potentially outdated: spec changed"). Atlas favours quick, possibly stale evidence over
|
|
@@ -3057,6 +3103,13 @@ when you do not know, and the viewer says the cause is unknown. It is never infe
|
|
|
3057
3103
|
failed before the behavior started comes from the failed step's `kind`: `hook`, `setup`, `fixture`, `before`,
|
|
3058
3104
|
`beforeAll`, `beforeEach` or `given` mean preparation; `when`, `then`, `assertion` or `expect` mean the behavior.
|
|
3059
3105
|
|
|
3106
|
+
### Placing steps in a recording
|
|
3107
|
+
|
|
3108
|
+
A video may declare how its clock relates to the check's under the `atlas.media` extension (`checkStartMs`, `pauses`,
|
|
3109
|
+
`marks`), so the viewer can seek it from a step and mark the step playing. Without it the video plays as ever and no step
|
|
3110
|
+
is placed. The fields, the rules and the link to a moment are in
|
|
3111
|
+
[Place steps in a recording](rich-evidence.md#place-steps-in-a-recording).
|
|
3112
|
+
|
|
3060
3113
|
## Attach evidence
|
|
3061
3114
|
|
|
3062
3115
|
### From the command line
|
|
@@ -3178,6 +3231,7 @@ Annotation `type`: `note`, `warning`, `deprecation`, `issue`, `accessibility`, `
|
|
|
3178
3231
|
| A kind of file Atlas has never seen | Attach it with its media type and a `role`. It is stored and offered for download today. |
|
|
3179
3232
|
| Structured data of your own | Put it in `extensions` under a name you own. It is kept, and shown as raw data. |
|
|
3180
3233
|
| A new annotation type, role or step kind | Use it. Open vocabularies need no registration. |
|
|
3234
|
+
| An automated reviewer's opinion of a captured UI | A [critique](critics.md): a part of kind `critique` with the `atlas.critique` extension. It is never counted and never gates. |
|
|
3181
3235
|
| Something that should be shown well | Ask for a renderer (below). |
|
|
3182
3236
|
| A field every tool should have | Open an issue. It is added as an optional field, and the rules above guarantee nothing breaks. |
|
|
3183
3237
|
|
|
@@ -3199,6 +3253,199 @@ listed with its name, role, type and size, and offered for download, and everyth
|
|
|
3199
3253
|
unaffected.
|
|
3200
3254
|
</doc>
|
|
3201
3255
|
|
|
3256
|
+
<doc path="docs/critics.md">
|
|
3257
|
+
# Critics: automated reviewers that mark which UIs need polish
|
|
3258
|
+
|
|
3259
|
+
A **critic** looks at one captured UI (an image and what is known about it) and says how polished it is, and ideally
|
|
3260
|
+
where and why it is not. A critic may be a pretrained model that gives a score, a vision-capable language model that
|
|
3261
|
+
follows a rubric, or a classic measure such as a contrast ratio. All three are the same thing to Atlas, through one small
|
|
3262
|
+
contract, so people review the likeliest problems first.
|
|
3263
|
+
|
|
3264
|
+
**Critics are producers that publish layers.** Atlas supplies the contract, the runner, the evidence format and the
|
|
3265
|
+
credential. It never executes a model on the server and never holds a provider key. A critique is not a test result and
|
|
3266
|
+
is not human review: it never changes whether a report passes.
|
|
3267
|
+
|
|
3268
|
+
- [What is available](#what-is-available)
|
|
3269
|
+
- [The critic contract](#the-critic-contract)
|
|
3270
|
+
- [A critique is a layer](#a-critique-is-a-layer)
|
|
3271
|
+
- [Running critics](#running-critics)
|
|
3272
|
+
- [State-aware judging, calibration and modes](#state-aware-judging-calibration-and-modes)
|
|
3273
|
+
- [Authentication and authorization](#authentication-and-authorization)
|
|
3274
|
+
- [Credentials](#credentials)
|
|
3275
|
+
- [What is not here yet](#what-is-not-here-yet)
|
|
3276
|
+
|
|
3277
|
+
## What is available
|
|
3278
|
+
|
|
3279
|
+
Import `@crvouga/atlas/critics`. It needs no model, no provider SDK and no network; a critic brings its own.
|
|
3280
|
+
|
|
3281
|
+
| You want | Use |
|
|
3282
|
+
| --- | --- |
|
|
3283
|
+
| Define a critic | `defineCritic` (the contract below) |
|
|
3284
|
+
| Judge many images with caching, retries, rate limits, timeouts and budgets | `runCritic` |
|
|
3285
|
+
| Store the opinions | `critiqueLayer`, `publishCritiques` (a layer of parts of kind `critique`) |
|
|
3286
|
+
| Read them back | `readCritique`, `critiquesOfLayer`, `critiqueCurrency` |
|
|
3287
|
+
| Read a score in context, measure a threshold, decide an exit code | `relativeVerdict`, `calibrate`, `gateDecision` |
|
|
3288
|
+
| Keep keys out of configuration and logs | `parseCredentialRef`, `resolveCredential`, `assertNoLiteralCredentials`, `createRedactor` |
|
|
3289
|
+
| Let a token publish critiques and nothing else | the `critic` role (below) |
|
|
3290
|
+
|
|
3291
|
+
The JSON Schema of the evidence is [`critique.schema.json`](../packages/schema/schemas/critique.schema.json). The
|
|
3292
|
+
function-level reference is generated from the code: [critics](reference/critics.md).
|
|
3293
|
+
|
|
3294
|
+
## The critic contract
|
|
3295
|
+
|
|
3296
|
+
```ts
|
|
3297
|
+
import { defineCritic } from '@crvouga/atlas/critics';
|
|
3298
|
+
|
|
3299
|
+
export default defineCritic({
|
|
3300
|
+
name: 'contrast', version: '1.0.0', kind: 'heuristic', // kind is an open string: model, llm, ml, heuristic…
|
|
3301
|
+
describe: () => ({ sendsImagesTo: null, needsSetup: false }),
|
|
3302
|
+
evaluate: async ({ image, imageSha256, entry, signal }) => ({
|
|
3303
|
+
verdict: { score: measureContrast(image), scale: '0..1, higher is better' }
|
|
3304
|
+
})
|
|
3305
|
+
});
|
|
3306
|
+
```
|
|
3307
|
+
|
|
3308
|
+
A critic only judges one image. `describe()` says where images are sent (`sendsImagesTo`, `null` when they never leave
|
|
3309
|
+
the machine), the model, and the fixed parameters (temperature, the SHA-256 of the rubric): together with the critic's
|
|
3310
|
+
`name` and `version` and the image hash they are the identity of a judgement. `evaluate` returns a `verdict` (a score
|
|
3311
|
+
and how to read it), `findings`, or both, and may throw: throw a `RateLimitedError` (with the provider's `Retry-After` in
|
|
3312
|
+
milliseconds), an `InvalidOutputError`, or any `CriticError` with your own reason code. The runner records the failure
|
|
3313
|
+
and the next run retries it. A failure never becomes a score.
|
|
3314
|
+
|
|
3315
|
+
## A critique is a layer
|
|
3316
|
+
|
|
3317
|
+
A critique is a check inside a part of kind `critique`, in a layer whose `producer` is `critic.<name>`. The check's
|
|
3318
|
+
`status` says only whether the critic ran: `passed` a verdict was produced, `failed` the critic could not produce one.
|
|
3319
|
+
The opinion is evidence, by the rules of [extending.md](extending.md#the-rules-that-keep-it-compatible-forever), so it
|
|
3320
|
+
can never decide a result. It lives in `extensions["atlas.critique"]` and is repeated as standard evidence, so a viewer
|
|
3321
|
+
that has never heard of critics still shows it: the score is a measurement named `polish`, and each finding is an
|
|
3322
|
+
annotation of type `critique`.
|
|
3323
|
+
|
|
3324
|
+
```json
|
|
3325
|
+
{
|
|
3326
|
+
"producer": "critic.vision-llm",
|
|
3327
|
+
"kind": "critique",
|
|
3328
|
+
"checks": [{
|
|
3329
|
+
"id": "critique/checkout/payment-sheet",
|
|
3330
|
+
"status": "passed",
|
|
3331
|
+
"measurements": [{ "name": "polish", "value": 0.62, "unit": "score" }],
|
|
3332
|
+
"annotations": [{ "type": "critique", "level": "warning", "message": "spacing: Label touches both edges of the button. (suggestion: Increase horizontal padding.)" }],
|
|
3333
|
+
"extensions": { "atlas.critique": {
|
|
3334
|
+
"schema": 1,
|
|
3335
|
+
"subject": { "entryKey": "checkout/payment-sheet", "imageSha256": "…", "state": "overlay" },
|
|
3336
|
+
"critic": { "name": "vision-llm", "kind": "llm", "version": "1.0.0" },
|
|
3337
|
+
"model": { "id": "…", "provider": "…" },
|
|
3338
|
+
"parameters": { "temperature": 0, "rubric": "<sha256 of the rubric>" },
|
|
3339
|
+
"verdict": { "score": 0.62, "scale": "0..1, higher is better", "label": "needs-polish", "confidence": 0.7 },
|
|
3340
|
+
"findings": [{ "id": "f1", "category": "spacing", "severity": "minor", "region": { "unit": "px", "x": 24, "y": 612, "w": 342, "h": 48 }, "rationale": "Label touches both edges of the button.", "suggestion": "Increase horizontal padding." }]
|
|
3341
|
+
} }
|
|
3342
|
+
}]
|
|
3343
|
+
}
|
|
3344
|
+
```
|
|
3345
|
+
|
|
3346
|
+
- A single score (a model-based critic) is a valid critique: everything except `schema`, `subject` and `critic` is optional.
|
|
3347
|
+
- `kind`, `category`, `severity`, `label` and failure `reason` are open strings, and every object is open. A value or field
|
|
3348
|
+
nobody has heard of is kept and shown generically, never rejected.
|
|
3349
|
+
- A reader that cannot read the whole extension (a missing `subject`, a score that is not a number, a `schema` version it
|
|
3350
|
+
does not know) drops the extension and keeps the check and its status. An unreadable opinion is unknown.
|
|
3351
|
+
- **Anchored to pixels.** `subject.imageSha256` is the hash of the image that was judged. When an entry's image changes,
|
|
3352
|
+
`critiqueCurrency` says the old critique judged an earlier image: it is shown, never hidden, and the entry counts as not
|
|
3353
|
+
yet judged.
|
|
3354
|
+
- **Slots.** The producer is the critic's name; the critic and model versions are labels. A newer version of a critic
|
|
3355
|
+
replaces an older one's critique of the same entry. Put the version in `parameters` of `critiqueLayer` to compare
|
|
3356
|
+
versions side by side instead.
|
|
3357
|
+
- **The prompt is recorded as a hash** (`parameters.rubric`), not as text.
|
|
3358
|
+
- **Failures are data.** A crash, a timeout, a rate limit that never cleared and an unparseable answer are `failed`
|
|
3359
|
+
critiques with `failure.reason` (`error`, `timeout`, `rate-limited`, `invalid-output`, `image-mismatch`…) and a message.
|
|
3360
|
+
|
|
3361
|
+
**Critiques never change the gate.** Parts of kind `critique` are left out of every count and of `atlas report status`,
|
|
3362
|
+
even when they are the only thing recorded (a report of opinions alone has no test results, so the gate says so). A low
|
|
3363
|
+
score does not fail it, and neither does a critic that crashed. A project that wants a score to fail something opts in
|
|
3364
|
+
with `gateDecision` in `gate` mode, which is computed by the reader from critique data and never changes a check's
|
|
3365
|
+
status, a slot or a winner.
|
|
3366
|
+
|
|
3367
|
+
## Running critics
|
|
3368
|
+
|
|
3369
|
+
`runCritic` owns everything around the judgement; a critic only judges one image.
|
|
3370
|
+
|
|
3371
|
+
- **Caching.** An entry is skipped, without fetching its image, when a critique with a verdict already exists for the same
|
|
3372
|
+
critic, version, model, parameters and image hash (`existing`, read with `critiquesOfLayer`). Identical images across
|
|
3373
|
+
runs are judged once; only a changed image is judged again. A failed critique is not a cache hit. `force` judges again.
|
|
3374
|
+
Layers are content-addressed, so publishing the same outcomes twice is harmless.
|
|
3375
|
+
- **Concurrency, timeout, rate limits.** A bounded number of calls in flight. A call over `timeoutMs` is aborted and
|
|
3376
|
+
recorded as `timeout`; the run goes on. A rate-limit answer waits at least `Retry-After`, with exponential backoff and
|
|
3377
|
+
jitter, for a bounded number of attempts, and then is recorded as `rate-limited`.
|
|
3378
|
+
- **Budgets.** `budget: { maxImages, maxTokens, maxCostUsd }`. When one is reached the run stops cleanly and returns what
|
|
3379
|
+
it finished with `stoppedBy: 'budget'`. Cost and tokens come from the `usage` a critic reports.
|
|
3380
|
+
- **Dry run.** `dryRun` loads and sends nothing and returns what would be judged and skipped, and where images would go
|
|
3381
|
+
(`sendsImagesTo`).
|
|
3382
|
+
- **Reproducibility.** Results come back in the order of the entries, so the same inputs publish the same layer; a
|
|
3383
|
+
deterministic critic gives identical scores on every run. Atlas records what explains a result (critic, version,
|
|
3384
|
+
model, rubric hash, parameters) and does not promise that a language model answers the same twice. A re-run adds a
|
|
3385
|
+
newer layer instead of changing an old one.
|
|
3386
|
+
- **Images may leave the machine.** `describe().sendsImagesTo` is the thing to show a person before a run that sends
|
|
3387
|
+
images to a third party, and to require explicit confirmation for.
|
|
3388
|
+
|
|
3389
|
+
```ts
|
|
3390
|
+
import { critiquesOfLayer, publishCritiques, runCritic } from '@crvouga/atlas/critics';
|
|
3391
|
+
|
|
3392
|
+
const result = await runCritic({
|
|
3393
|
+
critic, entries, loadImage: (entry) => fetchImage(entry),
|
|
3394
|
+
existing: layers.flatMap((layer) => critiquesOfLayer(layer).map((found) => found.critique)),
|
|
3395
|
+
budget: { maxImages: 200 }, secrets: [providerKey]
|
|
3396
|
+
});
|
|
3397
|
+
await publishCritiques(store, { critic, outcomes: result.outcomes, recordedAtMs: Date.now() });
|
|
3398
|
+
```
|
|
3399
|
+
|
|
3400
|
+
## State-aware judging, calibration and modes
|
|
3401
|
+
|
|
3402
|
+
A raw score is not absolute: a loading overlay is not a full screen.
|
|
3403
|
+
|
|
3404
|
+
- `relativeVerdict` reads a score against the project's scores of the **same state kind** (a percentile within the kind)
|
|
3405
|
+
and against the same entry's previous image (`delta`), and goes into `verdict.relative`.
|
|
3406
|
+
- `calibrate` measures a critic against labelled examples **per state kind**: sample sizes, precision, recall, the
|
|
3407
|
+
threshold chosen, and `inSample`, which is true unless held-out examples were given. In-sample figures flatter a critic
|
|
3408
|
+
and are labelled as such.
|
|
3409
|
+
- `gateDecision` turns scores into an exit code. Modes: `report` (default: shown, always exit 0), `warn` (a warning for each
|
|
3410
|
+
score below its threshold, exit 0), `gate` (exit 1 when a threshold is crossed). `gate` is **refused** (exit 2, with the
|
|
3411
|
+
reasons) unless every state kind present has a calibration of this critic and version with a threshold, at least 10 bad
|
|
3412
|
+
and 10 good examples, and held-out figures. An explicit threshold never waives that evidence.
|
|
3413
|
+
|
|
3414
|
+
## Authentication and authorization
|
|
3415
|
+
|
|
3416
|
+
Run critics against a server that has tokens: a server with no tokens is open and every caller is an administrator, a
|
|
3417
|
+
critic included.
|
|
3418
|
+
|
|
3419
|
+
A token of role `critic` (`ATLAS_CRITIC_TOKEN`, or `role: "critic"` under `auth.tokens`) may do what `read` may, and
|
|
3420
|
+
publish critiques, and nothing else. Through the store protocol (`PUT /api/store/…`) it may upload blobs and layers, and
|
|
3421
|
+
the catalog entries that make them visible, whose `producer` is `critic.<name>` and whose parts are all of kind
|
|
3422
|
+
`critique`. Anything else is `403` naming the rule it broke, and the layer is not stored. It cannot post test results,
|
|
3423
|
+
start live runs, change settings or remove anything. `publish` and `admin` tokens may publish critiques too. As for every
|
|
3424
|
+
token: send it as `Authorization: Bearer`, never in a URL, never over plain `http` beyond this machine.
|
|
3425
|
+
|
|
3426
|
+
## Credentials
|
|
3427
|
+
|
|
3428
|
+
Developers bring their own models and AI accounts. Configuration names a credential by **reference**
|
|
3429
|
+
(`env:SOME_API_KEY`, `file:/run/secrets/key`) and never contains one, so it can be committed;
|
|
3430
|
+
`assertNoLiteralCredentials` refuses a configuration with a literal key and says where, never what. Credentials are read at
|
|
3431
|
+
run time (`resolveCredential`) and every message the runner records goes through a redactor (`secrets` in `runCritic`,
|
|
3432
|
+
`createRedactor`) that removes them, in their URL-encoded and base64 forms too, and anything shaped like a provider key,
|
|
3433
|
+
token or JWT. A credential never reaches a layer, a progress event or an error message.
|
|
3434
|
+
|
|
3435
|
+
## What is not here yet
|
|
3436
|
+
|
|
3437
|
+
This release delivers the contract, the evidence format, the runner, state-aware judging and calibration, credential
|
|
3438
|
+
handling and the scoped `critic` role. Still to come, tracked in [issue 50](https://github.com/crvouga/atlas/issues/50):
|
|
3439
|
+
|
|
3440
|
+
- the **catalog read surface** for critics (cursor pagination, `since`, realtime events, images by hash), which builds on
|
|
3441
|
+
the UI catalog of [issue 43](https://github.com/crvouga/atlas/issues/43);
|
|
3442
|
+
- the **`atlas critic` command** (`list`, `setup`, `doctor`, `run`, `watch`, `review`, `calibrate`), its interactive mode
|
|
3443
|
+
and its `--format json` documents;
|
|
3444
|
+
- the **built-in UIClip critic** and the **LLM critic** with provider adapters;
|
|
3445
|
+
- the **viewer**: a ranked "needs polish" list, finding overlays and agreement across critics. Until then critiques are
|
|
3446
|
+
stored, composed and kept out of test counts, and the viewer lists only test results.
|
|
3447
|
+
</doc>
|
|
3448
|
+
|
|
3202
3449
|
<doc path="docs/report-backends.md">
|
|
3203
3450
|
|
|
3204
3451
|
</doc>
|
|
@@ -4139,6 +4386,28 @@ over the same filtered set the page lists, in the same order (`explorer/playlist
|
|
|
4139
4386
|
(paced, scripted, silent, synthetic) and the sentence that the length is the recording's, not production latency. Developer
|
|
4140
4387
|
details opens the test pinned to that run's record and closes the playlist.
|
|
4141
4388
|
|
|
4389
|
+
## Steps and moments in a recording
|
|
4390
|
+
|
|
4391
|
+
Wherever a result's steps sit beside its recording (a result's Timeline under Inspect, the Watch tab of a behavior, an
|
|
4392
|
+
opened watch playlist), `evidence/moments.tsx` joins them. Placement is from the recording's own declared timing
|
|
4393
|
+
(`evidence/media-timing.ts`, the `atlas.media` extension; see [Place steps in a recording](rich-evidence.md#place-steps-in-a-recording)),
|
|
4394
|
+
never from an assumption that the recording starts with the check.
|
|
4395
|
+
|
|
4396
|
+
- A step has a "Recording m:ss.s" button that seeks the selected recording; the step's test-clock offset stays visible beside it.
|
|
4397
|
+
A step that cannot be placed (no timing, no origin, no offset, outside the recording, unreadable declaration) is "Not aligned"
|
|
4398
|
+
and selecting it states the reason in a status alert. Nothing is guessed and the video is not moved.
|
|
4399
|
+
- While playing, the step under the playhead gets `aria-current="step"` and "Playing now". It never moves focus, scrolls
|
|
4400
|
+
the page or announces itself. Seeking only scrolls the player into view.
|
|
4401
|
+
- A paced recording (pauses declared, or a `pacing: paced` scope) says that gaps between steps include deliberate pauses and are
|
|
4402
|
+
not application latency.
|
|
4403
|
+
- A failed step has "Jump to the failing moment" in the bar above the timeline, and the failure summary has the same action
|
|
4404
|
+
(`requestFailureMoment`). If the recording is not on screen, or the step cannot be placed, it says so.
|
|
4405
|
+
- "Copy link to this moment" names the run (`layer`), the exact recording (`clip`) and the position (`clipAt`, ms), as the
|
|
4406
|
+
explorer address `/?test=…&layer=…&clip=…&clipAt=…` (the playlist links to the result it is playing). Opening it selects that
|
|
4407
|
+
recording of that exact run and seeks once the player knows its length; a position past the end or a recording that the run
|
|
4408
|
+
does not hold is reported and nothing else is played in its place. If the browser refuses to copy, the link is shown to copy.
|
|
4409
|
+
- One provider serves one holder: the check, or one try. A try's steps are placed by that try's recording and timing only.
|
|
4410
|
+
|
|
4142
4411
|
## Review collections
|
|
4143
4412
|
|
|
4144
4413
|
The **Collections** tab (`explorer/collections.tsx`, `?tab=collections&collection=<id>&rev=<n>`) lists and opens review
|
|
@@ -5197,7 +5466,7 @@ Responses: `200` It exists.; `401` No token, or an unknown one. Send `Authorizat
|
|
|
5197
5466
|
|
|
5198
5467
|
### Upload an object
|
|
5199
5468
|
|
|
5200
|
-
`PUT /api/store/{key}` · role:
|
|
5469
|
+
`PUT /api/store/{key}` · role: critic
|
|
5201
5470
|
|
|
5202
5471
|
The write half of the `ObjectStore` interface. The server checks what it can from the bytes in hand and says no to what would corrupt the report for everyone:
|
|
5203
5472
|
|
|
@@ -5209,6 +5478,8 @@ The write half of the `ObjectStore` interface. The server checks what it can fro
|
|
|
5209
5478
|
|
|
5210
5479
|
Upload in dependency order: blobs, then the manifest, then the catalog entry. `atlas report publish`, `atlas report stream` and `httpStore` do. Needs a `Bearer` token of role `publish` or `admin`; a browser's sign-in box is refused.
|
|
5211
5480
|
|
|
5481
|
+
A token of role `critic` may upload only what a critic publishes: blobs, and layers (and their catalog entries) whose `producer` is `critic.<name>` and whose parts are all of kind `critique`. Anything else is `403` with the rule it broke, and nothing is stored.
|
|
5482
|
+
|
|
5212
5483
|
- `key` (path, required): An object key under `layers/`, such as `layers/manifests/<id>.json`. It contains slashes, which are not percent-encoded; every other character is.
|
|
5213
5484
|
- `content-type` (header): Stored with the object and returned when it is read.
|
|
5214
5485
|
- `project` (query): Project slug. Alternatively send X-Atlas-Project or use the project-prefixed route. Omit for the default project. Conflicting selectors are refused.
|
|
@@ -5217,7 +5488,7 @@ Responses: `200` Already there with the same bytes.; `201` Stored.; `401` No tok
|
|
|
5217
5488
|
|
|
5218
5489
|
### Upload an object
|
|
5219
5490
|
|
|
5220
|
-
`PUT /api/projects/{project}/store/{key}` · role:
|
|
5491
|
+
`PUT /api/projects/{project}/store/{key}` · role: critic
|
|
5221
5492
|
|
|
5222
5493
|
The write half of the `ObjectStore` interface. The server checks what it can from the bytes in hand and says no to what would corrupt the report for everyone:
|
|
5223
5494
|
|
|
@@ -5229,6 +5500,8 @@ The write half of the `ObjectStore` interface. The server checks what it can fro
|
|
|
5229
5500
|
|
|
5230
5501
|
Upload in dependency order: blobs, then the manifest, then the catalog entry. `atlas report publish`, `atlas report stream` and `httpStore` do. Needs a `Bearer` token of role `publish` or `admin`; a browser's sign-in box is refused.
|
|
5231
5502
|
|
|
5503
|
+
A token of role `critic` may upload only what a critic publishes: blobs, and layers (and their catalog entries) whose `producer` is `critic.<name>` and whose parts are all of kind `critique`. Anything else is `403` with the rule it broke, and nothing is stored.
|
|
5504
|
+
|
|
5232
5505
|
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.
|
|
5233
5506
|
|
|
5234
5507
|
- `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.
|
package/llms.txt
CHANGED
|
@@ -21,6 +21,7 @@ Given only the repository link, start at docs/agents.md: install `@crvouga/atlas
|
|
|
21
21
|
|
|
22
22
|
- [The report contract](https://raw.githubusercontent.com/crvouga/atlas/main/docs/report-contract.md): This is the normative description of what Atlas reads and writes when it reports on specs of any kind:
|
|
23
23
|
- [Evidence: attachments, timelines, snapshots and everything else](https://raw.githubusercontent.com/crvouga/atlas/main/docs/extending.md): Atlas is opinionated about specs and open about evidence.
|
|
24
|
+
- [Critics: automated reviewers that mark which UIs need polish](https://raw.githubusercontent.com/crvouga/atlas/main/docs/critics.md): A critic looks at one captured UI (an image and what is known about it) and says how polished it is, and ideally where and why it is not.
|
|
24
25
|
- [docs/report-backends.md](https://raw.githubusercontent.com/crvouga/atlas/main/docs/report-backends.md):
|
|
25
26
|
- [Executable statecharts](https://raw.githubusercontent.com/crvouga/atlas/main/docs/statecharts.md): Write down how a product behaves as statecharts in open formats, then let Atlas lint them, plan paths that cover every transition, run those paths against the real product through any driver (a browser, a phone, a pla…
|
|
26
27
|
|
|
@@ -46,6 +47,7 @@ Given only the repository link, start at docs/agents.md: install `@crvouga/atlas
|
|
|
46
47
|
- [TypeScript API: detox](https://raw.githubusercontent.com/crvouga/atlas/main/docs/reference/detox.md): The Detox driver for statechart runs of React Native apps.
|
|
47
48
|
- [TypeScript API: runners](https://raw.githubusercontent.com/crvouga/atlas/main/docs/reference/runners.md): Native reporters that upload to the project server.
|
|
48
49
|
- [TypeScript API: vitest](https://raw.githubusercontent.com/crvouga/atlas/main/docs/reference/vitest.md): Run a statechart plan as Vitest tests.
|
|
50
|
+
- [TypeScript API: critics](https://raw.githubusercontent.com/crvouga/atlas/main/docs/reference/critics.md): Automated reviewers of the UI catalog: the critic contract, the runner (caching, retries, rate limits, budgets), credential references and redaction, the `atlas.critique` evidence as layers, calibration and report, warn and gate modes.
|
|
49
51
|
- [TypeScript API: playwright-reporter](https://raw.githubusercontent.com/crvouga/atlas/main/docs/reference/playwright-reporter.md): A Playwright Test reporter that streams every test to Atlas with its duration, device labels, steps, retries, videos, screenshots and traces.
|
|
50
52
|
- [JSON Schemas](https://unpkg.com/@crvouga/atlas/packages/schema/schemas/): the layer format, checks file, report status, command results, server responses, server configuration.
|
|
51
53
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crvouga/atlas",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.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",
|
|
@@ -85,6 +85,10 @@
|
|
|
85
85
|
"types": "./dist/server/src/index.d.ts",
|
|
86
86
|
"default": "./dist/server/src/index.js"
|
|
87
87
|
},
|
|
88
|
+
"./critics": {
|
|
89
|
+
"types": "./dist/critics/src/index.d.ts",
|
|
90
|
+
"default": "./dist/critics/src/index.js"
|
|
91
|
+
},
|
|
88
92
|
"./reports/*": {
|
|
89
93
|
"types": "./dist/reports/src/*.d.ts",
|
|
90
94
|
"default": "./dist/reports/src/*.js"
|
|
@@ -155,7 +159,8 @@
|
|
|
155
159
|
"docs/runners.md",
|
|
156
160
|
"packages/runners/compat",
|
|
157
161
|
"docs/ui.md",
|
|
158
|
-
"docs/workspaces.md"
|
|
162
|
+
"docs/workspaces.md",
|
|
163
|
+
"docs/critics.md"
|
|
159
164
|
],
|
|
160
165
|
"scripts": {
|
|
161
166
|
"build": "bun scripts/build-package.ts",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @crvouga/atlas/critics
|
|
2
|
+
|
|
3
|
+
Automated reviewers of the UI catalog that mark which UIs need polish. A critic judges one captured UI through one small
|
|
4
|
+
contract; the runner owns caching, retries, rate limits, timeouts and budgets; opinions are stored as ordinary layers of
|
|
5
|
+
parts of kind `critique` and never change whether a report passes.
|
|
6
|
+
|
|
7
|
+
Read [the critics guide](../../docs/critics.md). The function-level reference is generated from the code:
|
|
8
|
+
[critics](../../docs/reference/critics.md).
|
|
9
|
+
|
|
10
|
+
| Module | |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| `contract` | `defineCritic`, the `Critic` type, `CriticError`, `RateLimitedError`, `InvalidOutputError` |
|
|
13
|
+
| `runner` | `runCritic`: skip what was judged, bounded concurrency, timeout, backoff, budgets, dry run |
|
|
14
|
+
| `critique` | build, read and key critiques; `critiqueLayer`, `readCritique`, `critiqueCurrency` |
|
|
15
|
+
| `publish` | `publishCritiques` |
|
|
16
|
+
| `judging` | `relativeVerdict`, `calibrate`, `gateDecision` (report, warn, gate) |
|
|
17
|
+
| `credentials` | references (`env:NAME`), literal-credential refusal, redaction |
|
|
18
|
+
| `scope` | the rule a critic-role token is held to |
|