@crvouga/atlas 0.16.0 → 1.0.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 +12 -0
- package/README.md +52 -8
- package/dist/core/src/cli.d.ts +1 -1
- package/dist/core/src/cli.js +1 -1
- package/dist/reports/src/attachment-files.d.ts +3 -1
- package/dist/reports/src/attachment-files.js +14 -4
- package/dist/reports/src/evidence-types.d.ts +22 -0
- package/dist/reports/src/garbage.d.ts +39 -0
- package/dist/reports/src/garbage.js +82 -0
- package/dist/reports/src/layer-merge.js +5 -0
- package/dist/reports/src/layer-types.d.ts +1 -1
- package/dist/reports/src/publish-checks.js +11 -3
- package/dist/reports/src/recording-receipt.d.ts +48 -0
- package/dist/reports/src/recording-receipt.js +103 -0
- package/dist/reports/src/report-cli.d.ts +1 -1
- package/dist/reports/src/report-cli.js +20 -2
- package/dist/reports/src/report-index.d.ts +6 -0
- package/dist/reports/src/report-index.js +14 -0
- package/dist/reports/src/report-status.d.ts +2 -0
- package/dist/reports/src/report-status.js +17 -1
- package/dist/reports/src/reports-explorer-route.d.ts +8 -0
- package/dist/reports/src/reports-route.d.ts +8 -0
- package/dist/schema/src/api.d.ts +65 -0
- package/dist/schema/src/api.js +7 -2
- package/dist/schema/src/cli-results.d.ts +107 -2
- package/dist/schema/src/cli-results.js +11 -3
- package/dist/schema/src/index.d.ts +1 -0
- package/dist/schema/src/index.js +1 -0
- package/dist/schema/src/layer.d.ts +165 -0
- package/dist/schema/src/layer.js +72 -1
- package/dist/schema/src/server-config.d.ts +7 -49
- package/dist/schema/src/server-config.js +8 -21
- package/dist/schema/src/user-state.d.ts +115 -0
- package/dist/schema/src/user-state.js +54 -0
- package/dist/server/src/app.d.ts +16 -2
- package/dist/server/src/app.js +62 -0
- package/dist/server/src/config.d.ts +16 -21
- package/dist/server/src/config.js +48 -57
- package/dist/server/src/dev.d.ts +1 -1
- package/dist/server/src/dev.js +20 -7
- package/dist/server/src/index.d.ts +5 -1
- package/dist/server/src/index.js +5 -1
- package/dist/server/src/main.d.ts +23 -7
- package/dist/server/src/main.js +72 -26
- package/dist/server/src/openapi.js +24 -5
- package/dist/server/src/projects.js +89 -14
- package/dist/server/src/settings.d.ts +23 -7
- package/dist/server/src/settings.js +50 -60
- package/dist/server/src/storage.d.ts +68 -0
- package/dist/server/src/storage.js +125 -0
- package/dist/server/src/user-state.d.ts +31 -0
- package/dist/server/src/user-state.js +74 -0
- package/docs/agents.md +1 -1
- package/docs/deploying.md +38 -39
- package/docs/reference/cli.md +17 -7
- package/docs/reference/http-api.md +30 -2
- package/docs/reference/reports.md +165 -2
- package/docs/reference/schema.md +161 -16
- package/docs/reference/server.md +263 -41
- package/docs/report-backends.md +0 -144
- package/docs/report-contract.md +1 -1
- package/docs/rich-evidence.md +29 -0
- package/docs/runners.md +1 -1
- package/docs/server.md +141 -36
- package/docs/statecharts.md +1 -1
- package/docs/ui.md +1 -1
- package/docs/workspaces.md +5 -2
- package/llms-full.txt +265 -234
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/packages/reports/STORAGE.md +1 -1
- package/packages/schema/schemas/check-detail.schema.json +20 -0
- package/packages/schema/schemas/effective-delta.schema.json +61 -0
- package/packages/schema/schemas/effective-evidence.schema.json +61 -0
- package/packages/schema/schemas/evidence-page.schema.json +45 -0
- package/packages/schema/schemas/gc-policy.schema.json +36 -0
- package/packages/schema/schemas/gc-run-result.schema.json +106 -0
- package/packages/schema/schemas/gc-snapshot.schema.json +122 -0
- package/packages/schema/schemas/layer-rows.schema.json +16 -0
- package/packages/schema/schemas/layer.schema.json +153 -0
- package/packages/schema/schemas/publish-checks.schema.json +24 -0
- package/packages/schema/schemas/publish-result.schema.json +161 -1
- package/packages/schema/schemas/report-status.schema.json +104 -0
- package/packages/schema/schemas/server-config.schema.json +0 -41
- package/packages/schema/schemas/server-editable-settings.schema.json +0 -41
- package/packages/schema/schemas/server-settings-snapshot.schema.json +0 -82
- package/packages/schema/schemas/user-state.schema.json +140 -0
- package/packages/server/README.md +11 -6
- package/packages/server/openapi.json +843 -128
- package/packages/visualizer/src/admin/SettingsPage.tsx +183 -88
- package/packages/visualizer/src/evidence/marks.tsx +16 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module User state in S3
|
|
3
|
+
*
|
|
4
|
+
* Reads and writes the user state document (`UserStateSchema`: the garbage collection policy and the saved settings) as
|
|
5
|
+
* one JSON object at `server/state.json` in the object store, which in a running server is the S3 bucket. This is the
|
|
6
|
+
* only place user state is kept: not a file, not a database, not the browser. A missing object is an empty state
|
|
7
|
+
* ("nothing chosen yet"); an object that cannot be read or does not match the schema is an error that stops the
|
|
8
|
+
* server from starting and is never overwritten, so a transient or corrupt read can never silently reset anyone's choices.
|
|
9
|
+
* Saving refuses a stale revision (the SHA-256 of the stored text), serialised within this process. An object store has
|
|
10
|
+
* no conditional writes, so two servers saving at the same instant could still race; run one server per bucket.
|
|
11
|
+
*
|
|
12
|
+
* @see docs/server.md#user-state-and-garbage-collection
|
|
13
|
+
*/
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
import { readUtf8, writeUtf8 } from '../../reports/src/object-io.js';
|
|
16
|
+
import { UserStateSchema } from '../../schema/src/user-state.js';
|
|
17
|
+
/** Where the user state lives, relative to the store (the bucket plus `ATLAS_S3_PREFIX`). */
|
|
18
|
+
export const USER_STATE_KEY = 'server/state.json';
|
|
19
|
+
/** Raised when the stored user state cannot be trusted. The message says where it is and what to do. */
|
|
20
|
+
export class UserStateError extends Error {
|
|
21
|
+
}
|
|
22
|
+
const digest = (text) => createHash('sha256').update(text).digest('hex');
|
|
23
|
+
/**
|
|
24
|
+
* The user state kept in `store` at {@link USER_STATE_KEY}.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const userState = userStateStore(s3Backend.store);
|
|
28
|
+
* const { state } = await userState.read();
|
|
29
|
+
*/
|
|
30
|
+
export function userStateStore(store) {
|
|
31
|
+
let pending = Promise.resolve();
|
|
32
|
+
const read = async () => {
|
|
33
|
+
let text;
|
|
34
|
+
try {
|
|
35
|
+
text = await readUtf8(store, USER_STATE_KEY);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new UserStateError(`The user state ${USER_STATE_KEY} could not be read from S3: ${error instanceof Error ? error.message : String(error)}`);
|
|
39
|
+
}
|
|
40
|
+
if (text === null)
|
|
41
|
+
return { state: { schemaVersion: 1 }, revision: digest(''), exists: false };
|
|
42
|
+
let json;
|
|
43
|
+
try {
|
|
44
|
+
json = JSON.parse(text);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new UserStateError(`The user state ${USER_STATE_KEY} in S3 is not valid JSON. Atlas will not overwrite it: fix or delete that object, then restart.`);
|
|
48
|
+
}
|
|
49
|
+
const parsed = UserStateSchema.safeParse(json);
|
|
50
|
+
if (!parsed.success)
|
|
51
|
+
throw new UserStateError(`The user state ${USER_STATE_KEY} in S3 is invalid (${parsed.error.issues.map(issue => `${issue.path.join('.') || '(document)'}: ${issue.message}`).join('; ')}). Atlas will not overwrite it: fix or delete that object, then restart.`);
|
|
52
|
+
// The document as written, once it is known to be valid: defaults are not filled in, so a setting nobody chose stays unchosen.
|
|
53
|
+
return { state: json, revision: digest(text), exists: true };
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
read,
|
|
57
|
+
update(revision, change) {
|
|
58
|
+
const run = pending.then(async () => {
|
|
59
|
+
const held = await read();
|
|
60
|
+
if (held.revision !== revision)
|
|
61
|
+
throw new UserStateError('Settings changed. Reload them before saving.');
|
|
62
|
+
const next = { ...change(held.state), schemaVersion: 1 };
|
|
63
|
+
const checked = UserStateSchema.safeParse(next);
|
|
64
|
+
if (!checked.success)
|
|
65
|
+
throw new UserStateError(checked.error.issues.map(issue => `${issue.path.join('.') || '(document)'}: ${issue.message}`).join('; '));
|
|
66
|
+
const text = `${JSON.stringify(next, null, 2)}\n`;
|
|
67
|
+
await writeUtf8(store, USER_STATE_KEY, text, 'application/json');
|
|
68
|
+
return { state: next, revision: digest(text), exists: true };
|
|
69
|
+
});
|
|
70
|
+
pending = run.catch(() => undefined);
|
|
71
|
+
return run;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
package/docs/agents.md
CHANGED
|
@@ -144,7 +144,7 @@ workspace selection and test scripts. An existing `atlas.yml` not generated by A
|
|
|
144
144
|
workflows may still need manual integration. Non-JavaScript suites need their language/tool setup in CI.
|
|
145
145
|
|
|
146
146
|
Without a server, start with [local verification](quickstart.md). To run a server later, `atlas serve`
|
|
147
|
-
starts one (`http://127.0.0.1:8080
|
|
147
|
+
starts one (`http://127.0.0.1:8080`; it needs S3-compatible storage configured with the `ATLAS_S3_*` variables, see `.env.example`); [the server guide](server.md) covers configuration
|
|
148
148
|
and authentication. Keep synthetic failure checks in a scratch store rather than a shared production report.
|
|
149
149
|
|
|
150
150
|
## What an agent can rely on
|
package/docs/deploying.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
This is the production setup. A project runs **one Atlas server**. Developers' machines and every CI job send
|
|
4
4
|
their results to it, as they finish or when they are done, and it shows everyone, developers and the business
|
|
5
|
-
alike, the whole project at once, live. It keeps results
|
|
6
|
-
|
|
5
|
+
alike, the whole project at once, live. It keeps results (and all its user state) in an S3-compatible bucket and
|
|
6
|
+
nowhere else; nothing is partitioned by run, branch or person: results are layered. The server's API,
|
|
7
7
|
configuration and protocol are in **[the Atlas server](server.md)**; this page is the path to production.
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
a developer's worktree ─┐ ┌─▶ developers (browser, live)
|
|
11
|
-
a CI job, any branch ───┼─ atlas report publish|stream ─▶ SERVER ◀┤ atlas serve: keeps layers in
|
|
12
|
-
a device lab, nightly ──┘ (immutable layers, over HTTP)
|
|
11
|
+
a CI job, any branch ───┼─ atlas report publish|stream ─▶ SERVER ◀┤ atlas serve: keeps layers in S3
|
|
12
|
+
a device lab, nightly ──┘ (immutable layers, over HTTP) ──▶ S3 ├─▶ business (required, nothing else)
|
|
13
13
|
└─▶ CI gate atlas report status
|
|
14
14
|
```
|
|
15
15
|
|
|
@@ -18,7 +18,7 @@ Why this holds together:
|
|
|
18
18
|
- **Publishing never conflicts.** A layer is one immutable file named by its content. Two machines that
|
|
19
19
|
publish at once write different files; a retry writes the same one. There is no lock, no index to
|
|
20
20
|
corrupt, nothing to merge.
|
|
21
|
-
- **The server is replaceable.** What it knows is its
|
|
21
|
+
- **The server is replaceable.** What it knows is in its bucket. Replace it, restore it
|
|
22
22
|
from a backup, or run a copy on a laptop: they show the same report, because a report is a function of the
|
|
23
23
|
layers ([the contract](report-contract.md)).
|
|
24
24
|
- **One view.** The newest completed result of every check, in every environment, from anyone. A
|
|
@@ -36,13 +36,15 @@ Why this holds together:
|
|
|
36
36
|
|
|
37
37
|
## 1. Where results are kept
|
|
38
38
|
|
|
39
|
-
**
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
**The server keeps everything in one S3-compatible bucket** (reports and user state, such as the garbage collection
|
|
40
|
+
policy), set by the required `ATLAS_S3_ENDPOINT`, `ATLAS_S3_BUCKET`, `ATLAS_S3_REGION`, `ATLAS_S3_ACCESS_KEY_ID` and
|
|
41
|
+
`ATLAS_S3_SECRET_ACCESS_KEY` variables ([details](server.md#required-s3-storage), template in
|
|
42
|
+
[`.env.example`](https://github.com/crvouga/atlas/blob/main/.env.example)). Without them it refuses to start, listing
|
|
43
|
+
every variable and what is wrong. There is no disk or database to fall back to, so the server is replaceable:
|
|
44
|
+
a new one started on the same bucket shows the same report.
|
|
42
45
|
|
|
43
|
-
**
|
|
44
|
-
|
|
45
|
-
The rest of this section is that setup. A store is named by a URL. Keys never go in it.
|
|
46
|
+
**Clients and CI** name stores by URL, and a client may publish straight to a bucket or folder as well as to the
|
|
47
|
+
server. A store is named by a URL. Keys never go in it.
|
|
46
48
|
|
|
47
49
|
| Store | URL |
|
|
48
50
|
| --- | --- |
|
|
@@ -190,18 +192,16 @@ A run is uploaded only when its privacy scan passed: the same rule the static cl
|
|
|
190
192
|
## 4. The server
|
|
191
193
|
|
|
192
194
|
```sh
|
|
193
|
-
atlas serve #
|
|
195
|
+
atlas serve # ATLAS_S3_* says where; listens on :8080
|
|
194
196
|
```
|
|
195
197
|
|
|
196
|
-
It serves the client, the report and the project's specs and runs from
|
|
197
|
-
accepts uploads and announces new results on its event stream.
|
|
198
|
-
|
|
198
|
+
It serves the client, the report and the project's specs and runs from its bucket. It also
|
|
199
|
+
accepts uploads and announces new results on its event stream. The bucket's credentials need list, read, write and delete
|
|
200
|
+
permissions (`atlas report policy` prints a least-privilege policy; delete is for the garbage collection you define in the UI).
|
|
199
201
|
|
|
200
202
|
| Flag | Environment | Meaning |
|
|
201
203
|
| --- | --- | --- |
|
|
202
|
-
|
|
|
203
|
-
| `--data <dir>` | `ATLAS_DATA` | Local upload backend. Default `./atlas-data`. |
|
|
204
|
-
| `--write <id>` | `ATLAS_WRITE` | Backend that accepts uploads. Default `data`. |
|
|
204
|
+
| | `ATLAS_S3_ENDPOINT`, `ATLAS_S3_BUCKET`, `ATLAS_S3_REGION`, `ATLAS_S3_ACCESS_KEY_ID`, `ATLAS_S3_SECRET_ACCESS_KEY` | **Required.** The S3-compatible bucket that holds all reports and user state. Optional: `ATLAS_S3_PREFIX`, `ATLAS_S3_PATH_STYLE`, `ATLAS_S3_SESSION_TOKEN`. |
|
|
205
205
|
| `--port <n>` | `ATLAS_PORT` or `PORT` | Default 8080. `0` picks a free one. |
|
|
206
206
|
| `--host <h>` | `ATLAS_HOST` or `HOST` | Default `127.0.0.1`. Listening beyond this machine requires tokens or `--insecure`. |
|
|
207
207
|
| `--site <dir>` | `ATLAS_SITE` | The built client. Default: `./atlas-site`, else built once into `~/.cache/atlas/site`. |
|
|
@@ -228,9 +228,10 @@ updated `latest`. Pull request builds and failed publications are labelled accor
|
|
|
228
228
|
Leave the start command unset: the image includes the server and the prebuilt viewer.
|
|
229
229
|
2. Add `ATLAS_TOKEN` with a long random value (generate one with `openssl rand -hex 24`). It grants admin
|
|
230
230
|
access; optionally add separate `ATLAS_PUBLISH_TOKEN` and `ATLAS_READ_TOKEN` for publishers and readers.
|
|
231
|
-
3.
|
|
232
|
-
|
|
233
|
-
|
|
231
|
+
3. Add the five required storage variables (`ATLAS_S3_ENDPOINT`, `ATLAS_S3_BUCKET`, `ATLAS_S3_REGION`,
|
|
232
|
+
`ATLAS_S3_ACCESS_KEY_ID`, `ATLAS_S3_SECRET_ACCESS_KEY`) for a bucket you own. The service keeps nothing on its own
|
|
233
|
+
disk, so no volume is needed and a replaced image loses nothing; without the variables it exits and lists each one.
|
|
234
|
+
Keep one replica.
|
|
234
235
|
4. Set the healthcheck path to `/readyz` and generate a public domain. The image listens on `0.0.0.0` and
|
|
235
236
|
honors Railway's `PORT`; leave `ATLAS_PORT` unset. Open the domain and sign in with your token as the
|
|
236
237
|
password (any username), or use the viewer's token sign-in.
|
|
@@ -244,8 +245,7 @@ Projects publish to that domain with `ATLAS_STORE=https://YOUR-DOMAIN` and their
|
|
|
244
245
|
[sending results](#3-send-results-from-ci-and-from-laptops). Hosting Atlas needs no project checkout; publishing
|
|
245
246
|
your project's test results still needs the integration described there.
|
|
246
247
|
|
|
247
|
-
|
|
248
|
-
and the bucket credentials. Keep the `/data` volume for server settings and local state.
|
|
248
|
+
Then open `/admin` to define the garbage collection policy; until you save one, nothing is deleted.
|
|
249
249
|
|
|
250
250
|
### Deploy without authentication
|
|
251
251
|
|
|
@@ -261,12 +261,11 @@ In Railway, create a **Docker image** service with:
|
|
|
261
261
|
ghcr.io/crvouga/atlas-insecure:latest
|
|
262
262
|
```
|
|
263
263
|
|
|
264
|
-
Leave the start command unset.
|
|
264
|
+
Leave the start command unset. Add the required `ATLAS_S3_*` storage variables, keep one
|
|
265
265
|
replica, set the healthcheck to `/readyz` and generate a public domain. Leave `ATLAS_PORT` unset so Railway's
|
|
266
266
|
`PORT` is used. Enable **Settings → Source → Configure Auto Updates → Anytime** to follow `latest`.
|
|
267
267
|
Do not set `ATLAS_TOKEN`, `ATLAS_PUBLISH_TOKEN` or `ATLAS_READ_TOKEN`, or add `auth.tokens` to
|
|
268
|
-
|
|
269
|
-
authenticated deployment, remove its saved auth tokens first. Open the domain directly, including `/admin`
|
|
268
|
+
a configuration file: configured tokens still enable authentication. Open the domain directly, including `/admin`
|
|
270
269
|
for settings (leave the token field blank and choose **Load settings**).
|
|
271
270
|
|
|
272
271
|
Verify anonymous reads and uploads without an Authorization header:
|
|
@@ -287,7 +286,7 @@ leave its `ATLAS_TOKEN` unset. Use the same publishing commands described in
|
|
|
287
286
|
For local Docker:
|
|
288
287
|
|
|
289
288
|
```sh
|
|
290
|
-
docker run --pull always -p 8080:8080 -
|
|
289
|
+
docker run --pull always -p 8080:8080 --env-file .env ghcr.io/crvouga/atlas-insecure:latest
|
|
291
290
|
```
|
|
292
291
|
|
|
293
292
|
Both images publish full commit tags for pinning or rollback. A new GHCR package must be made Public
|
|
@@ -297,10 +296,11 @@ once before Railway can pull without registry credentials; see
|
|
|
297
296
|
### Docker
|
|
298
297
|
|
|
299
298
|
The same published image works anywhere Docker runs. It already contains the client (the same
|
|
300
|
-
for every project: the server tells the page where its data is), and keeps
|
|
299
|
+
for every project: the server tells the page where its data is), and keeps nothing on its own disk: it needs the
|
|
300
|
+
`ATLAS_S3_*` variables (copy `.env.example` to `.env` and fill it in):
|
|
301
301
|
|
|
302
302
|
```sh
|
|
303
|
-
docker run --pull always -p 8080:8080 -
|
|
303
|
+
docker run --pull always -p 8080:8080 --env-file .env \
|
|
304
304
|
-e ATLAS_TOKEN=$(openssl rand -hex 24) ghcr.io/crvouga/atlas:latest
|
|
305
305
|
```
|
|
306
306
|
|
|
@@ -309,17 +309,15 @@ back, replace `latest` with the full commit SHA. To build from this repository,
|
|
|
309
309
|
[`examples/deploy/Dockerfile`](https://github.com/crvouga/atlas/blob/main/examples/deploy/Dockerfile) is the
|
|
310
310
|
alternative for building from an npm version or a local package tarball.
|
|
311
311
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
instance and set no keys.
|
|
312
|
+
The bucket's account needs the combined **both** policy (including `server/live/*` for producer leases and `server/state.json`
|
|
313
|
+
for user state) plus `s3:DeleteObject` for garbage collection. The server does not use ambient AWS roles: pass the keys.
|
|
315
314
|
|
|
316
315
|
**Try it on one machine.** [`examples/deploy/compose.yaml`](https://github.com/crvouga/atlas/blob/main/examples/deploy/compose.yaml)
|
|
317
|
-
starts the published image
|
|
316
|
+
starts the published image with an S3 server (SeaweedFS) and a bucket as its storage; `compose.s3.yaml` is the same stack without tokens.
|
|
318
317
|
Publish to `http://localhost:8080` from your project and open it.
|
|
319
318
|
|
|
320
319
|
**Kubernetes.** [`examples/deploy/kubernetes.yaml`](https://github.com/crvouga/atlas/blob/main/examples/deploy/kubernetes.yaml):
|
|
321
|
-
one copy (the server is a singleton for the project), results in the bucket, readiness on `/readyz
|
|
322
|
-
service account.
|
|
320
|
+
one copy (the server is a singleton for the project), results in the bucket (keys from a Secret), readiness on `/readyz`.
|
|
323
321
|
|
|
324
322
|
**Without a container.** `bun add -d @crvouga/atlas`, then `bunx atlas client build --no-data --out
|
|
325
323
|
atlas-site` and `bunx atlas serve`. Everything it can be told is in
|
|
@@ -392,12 +390,13 @@ Narrow it by choice, per consumer, never by moving data:
|
|
|
392
390
|
listing (one request per thousand layers) runs at the start and every 30 refreshes (five minutes by
|
|
393
391
|
default), which is also how a layer recorded with an old timestamp is found. One small `GET` per new
|
|
394
392
|
layer, once.
|
|
395
|
-
- **Retention.** Atlas deletes nothing
|
|
396
|
-
|
|
393
|
+
- **Retention.** Atlas deletes nothing until you define a garbage collection policy in the UI (`/admin`); it is kept in the bucket as JSON and
|
|
394
|
+
then removes only superseded layers older than you allow ([details](server.md#user-state-and-garbage-collection)). `atlas report doctor` shows what the bucket holds.
|
|
395
|
+
- **Disaster recovery.** The bucket is the only state (reports and user state). Back it up, replicate or version it as you
|
|
397
396
|
do any data; a server is rebuilt from nothing in the time it takes to read the layers (below). It listens at once
|
|
398
397
|
and answers `/readyz` 503 until it has, so a liveness probe never kills one that is still reading.
|
|
399
|
-
- **A second bucket.**
|
|
400
|
-
report,
|
|
398
|
+
- **A second bucket.** The server reads one bucket. To combine another team's layers or an archive into the
|
|
399
|
+
report, copy them in with `atlas report merge --into <store>`; clients and `atlas report status` may read several stores.
|
|
401
400
|
|
|
402
401
|
## 7. Performance
|
|
403
402
|
|
package/docs/reference/cli.md
CHANGED
|
@@ -16,7 +16,7 @@ atlas — executable statecharts
|
|
|
16
16
|
[--no-retry] [--output <dir>]
|
|
17
17
|
atlas merge [--config atlas.config.ts] [--output <dir>] <run-dir> <run-dir>...
|
|
18
18
|
atlas export --specs <dir> --format scxml|mermaid|markdown|json [--out <file>]
|
|
19
|
-
atlas server [--
|
|
19
|
+
atlas server [--port 8080] one Atlas node: API, projects and client; needs ATLAS_S3_* (atlas serve --help)
|
|
20
20
|
atlas client build [--no-data] [--out <dir>] build the project-agnostic Atlas client
|
|
21
21
|
atlas dev [--port 5180] develop the Atlas server and client on one host
|
|
22
22
|
atlas init [--server <url>] [--write] integrate this project: detect its suites, print the plan (atlas init --help)
|
|
@@ -53,14 +53,18 @@ atlas init — integrate a project with an Atlas server
|
|
|
53
53
|
```text
|
|
54
54
|
atlas serve: the Atlas server
|
|
55
55
|
|
|
56
|
-
atlas serve [--config atlas.server.json] [--
|
|
57
|
-
[--store <url|id=url>]... [--write <id>] [--site <dir>]
|
|
56
|
+
atlas serve [--config atlas.server.json] [--port 8080] [--host 127.0.0.1] [--site <dir>]
|
|
58
57
|
[--outline-branch <b>] [--branch <name>]... [--ignore-modified] [--refresh <seconds>]
|
|
59
58
|
[--anonymous-read] [--insecure] [--no-log]
|
|
60
59
|
|
|
61
60
|
One process serves the client and the API. Test results are uploaded to it from anywhere (laptops, CI) and it
|
|
62
|
-
shows everyone the combined report, live.
|
|
63
|
-
|
|
61
|
+
shows everyone the combined report, live.
|
|
62
|
+
|
|
63
|
+
Storage is required. ALL reports and ALL user state (the garbage collection policy, saved settings) are kept in
|
|
64
|
+
S3-compatible object storage and nowhere else; there is no local-disk, in-memory or database fallback. Without
|
|
65
|
+
these environment variables the server prints every one of them with what is wrong, and exits with status 2:
|
|
66
|
+
ATLAS_S3_ENDPOINT ATLAS_S3_BUCKET ATLAS_S3_REGION ATLAS_S3_ACCESS_KEY_ID ATLAS_S3_SECRET_ACCESS_KEY
|
|
67
|
+
Optional: ATLAS_S3_PREFIX, ATLAS_S3_PATH_STYLE, ATLAS_S3_SESSION_TOKEN. See .env.example.
|
|
64
68
|
|
|
65
69
|
Tokens (ATLAS_TOKEN admin, ATLAS_PUBLISH_TOKEN, ATLAS_READ_TOKEN, or "auth.tokens" in the config) are required
|
|
66
70
|
to listen beyond this machine unless --insecure. Every option has an environment variable (docs/deploying.md).
|
|
@@ -71,11 +75,12 @@ atlas serve: the Atlas server
|
|
|
71
75
|
```text
|
|
72
76
|
atlas dev: the Atlas server and the client, for working on either
|
|
73
77
|
|
|
74
|
-
atlas dev [--port 5180] [--host 127.0.0.1] [--
|
|
78
|
+
atlas dev [--port 5180] [--host 127.0.0.1] [--config atlas.server.json]
|
|
75
79
|
|
|
76
80
|
Starts the server (API only, on a port of its own) and the client's dev server, which proxies /api to it, so the
|
|
77
81
|
browser talks to one host and a change to the client shows at once. In production `atlas serve` does both from
|
|
78
|
-
one process.
|
|
82
|
+
one process. Storage is the same as for `atlas serve`: the required ATLAS_S3_* environment variables name the S3
|
|
83
|
+
bucket that holds all reports and user state; without them it exits with status 2 and lists every variable.
|
|
79
84
|
```
|
|
80
85
|
|
|
81
86
|
## atlas report
|
|
@@ -91,6 +96,7 @@ atlas report — layered evidence for any kind of spec
|
|
|
91
96
|
[--param key=value]... [--kind <k>] [--title <t>] [--recorded-at <iso|ms>]
|
|
92
97
|
[--attach [role=]<file>]... [--label key=value]... [--link [name=]<url>]...
|
|
93
98
|
[--attach-root <dir>]... [--max-attachment-mb <n>] [--max-total-attachment-mb <n>]
|
|
99
|
+
[--require-recordings]
|
|
94
100
|
atlas report stream [--store <dir|s3://…|https://atlas.example.com>] --producer <name> [--environment <name>]
|
|
95
101
|
[--branch <b>] [--sha <s>] [--run-url <u>] [--kind <k>] [--param key=value]...
|
|
96
102
|
[--run-id <id>] [--run-title <t>]
|
|
@@ -127,6 +133,10 @@ atlas report — layered evidence for any kind of spec
|
|
|
127
133
|
Files a report names (CTRF attachments, JUnit [[ATTACHMENT|path]] lines) come from the report's own
|
|
128
134
|
folder and each --attach-root, and never from a hidden file or a link out: a report is data from a
|
|
129
135
|
test run, not instructions. One that is missing or not allowed is skipped with a warning.
|
|
136
|
+
--require-recordings exit 1 when a check that declared recordingExpectation "required" has no
|
|
137
|
+
uploaded video (absent, left out as too large or not allowed, or refused). The layer is
|
|
138
|
+
still written and the checks keep their results; only this command fails. The receipt of
|
|
139
|
+
what was uploaded (counts, sizes, checksums) is in the JSON result under layer.recordings.
|
|
130
140
|
From a checkout with uncommitted changes the layer is marked "modified".
|
|
131
141
|
stream Send checks as they finish: one JSON object per line on standard input (the fields of one
|
|
132
142
|
check: id, title, status, and any evidence). Each second's worth becomes one layer, so a server
|
|
@@ -116,7 +116,7 @@ Responses: `200` Settings editor.; `401` No token, or an unknown one. Send `Auth
|
|
|
116
116
|
|
|
117
117
|
`GET /api/settings` · role: admin
|
|
118
118
|
|
|
119
|
-
Returns editable
|
|
119
|
+
Returns the editable report and live-run settings saved in the user state in S3, active settings, a revision, and configuration precedence. Storage and authentication settings are excluded. Requires an admin Bearer token. Embedded applications may manage settings outside this API.
|
|
120
120
|
|
|
121
121
|
Responses: `200` Settings and revision.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `403` The token is known but its role may not do this.; `503` Configuration is managed by the embedding application.
|
|
122
122
|
|
|
@@ -124,12 +124,40 @@ Responses: `200` Settings and revision.; `401` No token, or an unknown one. Send
|
|
|
124
124
|
|
|
125
125
|
`PUT /api/settings` · role: admin
|
|
126
126
|
|
|
127
|
-
Replaces editable
|
|
127
|
+
Replaces the editable report and live-run settings in the user state in S3 (`server/state.json`), preserving the garbage collection policy and other fields. Persists a validated configuration; no report data is moved. Environment variables and flags override these values after restart. Requires an admin Bearer token, same-origin requests in browsers, and a current revision.
|
|
128
128
|
|
|
129
129
|
- `If-Match` (header, required): The quoted revision returned by GET /api/settings.
|
|
130
130
|
|
|
131
131
|
Responses: `200` Saved settings and new revision. Restart required.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `403` The token is known but its role may not do this.; `409` Settings changed; reload before saving.; `413` Settings exceed the body limit.; `422` Invalid configuration.; `428` A quoted settings revision is required.; `503` Configuration is managed by the embedding application.
|
|
132
132
|
|
|
133
|
+
### Read the garbage collection policy
|
|
134
|
+
|
|
135
|
+
`GET /api/settings/gc` · role: admin
|
|
136
|
+
|
|
137
|
+
Returns the policy kept in the user state in S3 (`server/state.json`), or `policy: null, saved: false` when none has been saved yet, in which case garbage collection is off and nothing is deleted. Also returns the revision to send back when saving and the last pass over each project. Requires an admin Bearer token. If the stored user state is unreadable the answer is 503 and the stored object is left untouched.
|
|
138
|
+
|
|
139
|
+
Responses: `200` The policy, its revision and the last passes.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `403` The token is known but its role may not do this.; `503` The user state in S3 cannot be read, or settings are managed by the embedding application.
|
|
140
|
+
|
|
141
|
+
### Save the garbage collection policy
|
|
142
|
+
|
|
143
|
+
`PUT /api/settings/gc` · role: admin
|
|
144
|
+
|
|
145
|
+
Replaces the policy in the user state in S3 and leaves the rest of that document alone. The server reads the policy from S3 each time it collects, so the new policy applies at the next pass, without a restart. Requires an admin Bearer token, same-origin requests in browsers, and the current revision.
|
|
146
|
+
|
|
147
|
+
- `If-Match` (header, required): The quoted revision returned by GET /api/settings/gc.
|
|
148
|
+
|
|
149
|
+
Responses: `200` The saved policy and the new revision.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `403` The token is known but its role may not do this.; `409` The state changed; reload before saving.; `413` The body exceeds the limit.; `422` Invalid policy.; `428` A quoted revision is required.; `503` The user state in S3 cannot be read, or settings are managed by the embedding application.
|
|
150
|
+
|
|
151
|
+
### Collect garbage now, or preview it
|
|
152
|
+
|
|
153
|
+
`POST /api/settings/gc/run` · role: admin
|
|
154
|
+
|
|
155
|
+
Applies the policy saved in S3 to every project now, instead of waiting for the schedule. Only report layers that were read and no longer decide any result, older than the policy allows and beyond its `keepLatest`, are deleted, with the captured files only they used. With `dryRun=1` nothing is deleted and each run reports what it would delete (a disabled policy is previewed as if enabled). Answers 409 when no policy has been saved, and, without `dryRun`, when the saved policy is disabled. Requires an admin Bearer token and a same-origin request in browsers.
|
|
156
|
+
|
|
157
|
+
- `dryRun` (query): `1` or `true` to only count what would be deleted.
|
|
158
|
+
|
|
159
|
+
Responses: `200` One run per project.; `401` No token, or an unknown one. Send `Authorization: Bearer <token>`.; `403` The token is known but its role may not do this.; `409` No policy has been saved, or it is disabled.
|
|
160
|
+
|
|
133
161
|
### Is the process up
|
|
134
162
|
|
|
135
163
|
`GET /healthz` · role: none
|
|
@@ -276,7 +276,9 @@ recursively), and hands their bytes over as `data` with a `contentType` guessed
|
|
|
276
276
|
attachment gave none.
|
|
277
277
|
|
|
278
278
|
A file that is missing, not allowed or too large is left out with a warning: evidence that could not be
|
|
279
|
-
found never stops the results from being published.
|
|
279
|
+
found never stops the results from being published. The exception in kind is a video of a check that declared
|
|
280
|
+
`recordingExpectation: "required"`: it stays as an attachment without bytes whose `omitted` says why (`not-found`,
|
|
281
|
+
`not-allowed`, `too-large`, `over-budget`), so the recording receipt can name the gap. Attachments without a `path` are kept untouched.
|
|
280
282
|
|
|
281
283
|
- `holder`: A check, step, attempt or any object that may have `attachments`, `steps` and `attempts`.
|
|
282
284
|
|
|
@@ -1747,11 +1749,34 @@ export type LayerHeader = {
|
|
|
1747
1749
|
readonly execution: string;
|
|
1748
1750
|
/** What the run as a whole carries: its logs, links and labels. */
|
|
1749
1751
|
readonly evidence?: EvidenceSummary;
|
|
1752
|
+
/** Counts from the layer's recording receipt: how many expected recordings were published. Absent when the layer declared no expectations: its completeness is unknown, not complete. Never changes a status. */
|
|
1753
|
+
readonly recordings?: RecordingCounts;
|
|
1750
1754
|
};
|
|
1751
1755
|
```
|
|
1752
1756
|
|
|
1753
1757
|
A report layer's origin and totals. Its checks are read separately, once.
|
|
1754
1758
|
|
|
1759
|
+
#### `RecordingCounts`
|
|
1760
|
+
|
|
1761
|
+
```ts
|
|
1762
|
+
export type RecordingCounts = {
|
|
1763
|
+
/** Checks that required a recording: `uploaded + referenced + skipped + unresolved`. */
|
|
1764
|
+
readonly expected: number;
|
|
1765
|
+
/** Stored in the layer, with a checksum. */
|
|
1766
|
+
readonly uploaded: number;
|
|
1767
|
+
/** Given only by `uri`: Atlas cannot vouch for it. */
|
|
1768
|
+
readonly referenced: number;
|
|
1769
|
+
/** Captured but left out, with a reason (too large, not allowed…). */
|
|
1770
|
+
readonly skipped: number;
|
|
1771
|
+
/** No video was attached at all. */
|
|
1772
|
+
readonly unresolved: number;
|
|
1773
|
+
/** Intentionally unrecorded checks: neither expected nor missing. */
|
|
1774
|
+
readonly notApplicable: number;
|
|
1775
|
+
};
|
|
1776
|
+
```
|
|
1777
|
+
|
|
1778
|
+
How many recordings a layer expected and what became of them (see `RecordingReceipt`).
|
|
1779
|
+
|
|
1755
1780
|
#### `LayerRow`
|
|
1756
1781
|
|
|
1757
1782
|
```ts
|
|
@@ -1775,6 +1800,8 @@ export type LayerRow = {
|
|
|
1775
1800
|
readonly durationMs: number | null;
|
|
1776
1801
|
/** What this result carries beyond its outcome. Absent when nothing. */
|
|
1777
1802
|
readonly evidence?: EvidenceSummary;
|
|
1803
|
+
/** What became of this check's expected recording: `uploaded`, `referenced`, `skipped`, `unresolved` or `not-applicable`, with a reason when it is not uploaded. Absent when the check declared no expectation. Independent of `status`. */
|
|
1804
|
+
readonly recording?: { readonly state: string; readonly reason?: string };
|
|
1778
1805
|
};
|
|
1779
1806
|
```
|
|
1780
1807
|
|
|
@@ -2775,6 +2802,76 @@ export function neighborStepFrame(
|
|
|
2775
2802
|
|
|
2776
2803
|
First frame of the next / previous step (skipping same-step frames).
|
|
2777
2804
|
|
|
2805
|
+
### Layer garbage collection
|
|
2806
|
+
|
|
2807
|
+
Source: `packages/reports/src/garbage.ts`. Import: `@crvouga/atlas/reports/garbage`
|
|
2808
|
+
|
|
2809
|
+
Applies a garbage collection policy (`GcPolicy`, chosen by a person in the Atlas UI and kept in the user state) to the
|
|
2810
|
+
layers in a store. Atlas is otherwise deletion-free, so this is deliberately conservative: a layer is deleted only when
|
|
2811
|
+
(1) the caller says the report has read it and every result it carries has been replaced by a newer layer (`eligible`),
|
|
2812
|
+
(2) it was recorded longer ago than the policy allows, and (3) it is not among the newest `keepLatest` layers. Deleting
|
|
2813
|
+
it therefore cannot change the combined report. Its catalog entry goes first, so nothing visible ever points at a
|
|
2814
|
+
missing manifest; its manifest next; then the captured-file blobs that no remaining layer refers to. If any remaining
|
|
2815
|
+
layer cannot be read, no blob is deleted: what cannot be read is unknown, never unused. Specs, runs and commit
|
|
2816
|
+
inventories are not touched.
|
|
2817
|
+
|
|
2818
|
+
See docs/server.md#user-state-and-garbage-collection
|
|
2819
|
+
|
|
2820
|
+
#### `GcCandidate`
|
|
2821
|
+
|
|
2822
|
+
```ts
|
|
2823
|
+
export type GcCandidate = { readonly id: string; readonly catalogKey: string; readonly recordedAtMs: number };
|
|
2824
|
+
```
|
|
2825
|
+
|
|
2826
|
+
One layer the policy would delete.
|
|
2827
|
+
|
|
2828
|
+
#### `GcPlan`
|
|
2829
|
+
|
|
2830
|
+
```ts
|
|
2831
|
+
export type GcPlan = {
|
|
2832
|
+
/** Layers to delete, oldest first. */
|
|
2833
|
+
readonly candidates: readonly GcCandidate[];
|
|
2834
|
+
/** Layers in the catalog that stay. */
|
|
2835
|
+
readonly kept: number;
|
|
2836
|
+
};
|
|
2837
|
+
```
|
|
2838
|
+
|
|
2839
|
+
What a policy would delete from a store right now.
|
|
2840
|
+
|
|
2841
|
+
#### `GcOutcome`
|
|
2842
|
+
|
|
2843
|
+
```ts
|
|
2844
|
+
export type GcOutcome = { readonly deletedLayers: number; readonly deletedBlobs: number };
|
|
2845
|
+
```
|
|
2846
|
+
|
|
2847
|
+
The outcome of applying a plan.
|
|
2848
|
+
|
|
2849
|
+
#### `planGarbage`
|
|
2850
|
+
|
|
2851
|
+
```ts
|
|
2852
|
+
export async function planGarbage(store: ObjectStore, input: { readonly policy: GcPolicy; readonly nowMs: number; readonly eligible: ReadonlySet<string> }): Promise<GcPlan>
|
|
2853
|
+
```
|
|
2854
|
+
|
|
2855
|
+
Decides which layers a policy deletes, without deleting anything. `eligible` is the set of layer ids the caller has
|
|
2856
|
+
read and knows no longer decide any result (`ReportIndex.superseded()`); a layer not in it is never a candidate.
|
|
2857
|
+
Lists the catalog only. With `policy.enabled === false` the plan is empty.
|
|
2858
|
+
|
|
2859
|
+
**Example**
|
|
2860
|
+
|
|
2861
|
+
```ts
|
|
2862
|
+
const plan = await planGarbage(store, { policy, nowMs: Date.now(), eligible: index.superseded() });
|
|
2863
|
+
```
|
|
2864
|
+
|
|
2865
|
+
#### `collectGarbage`
|
|
2866
|
+
|
|
2867
|
+
```ts
|
|
2868
|
+
export async function collectGarbage(store: ObjectStore, plan: GcPlan): Promise<GcOutcome>
|
|
2869
|
+
```
|
|
2870
|
+
|
|
2871
|
+
Deletes the layers of a plan, then the blobs only they used. Store errors throw after whatever was already deleted;
|
|
2872
|
+
running the same plan again finishes the job, because every delete is idempotent and the catalog entry (what
|
|
2873
|
+
makes a layer visible) always goes before the content.
|
|
2874
|
+
|
|
2778
2875
|
### URL hash routes of the reports viewer
|
|
2779
2876
|
|
|
2780
2877
|
Source: `packages/reports/src/hash.ts`. Import: `@crvouga/atlas/reports/hash`
|
|
@@ -3983,7 +4080,7 @@ String choices (such as `{ device: 'iphone-15' }`) that make a result its own sl
|
|
|
3983
4080
|
#### `LayerExtras`
|
|
3984
4081
|
|
|
3985
4082
|
```ts
|
|
3986
|
-
export type LayerExtras = Partial<Pick<import('../../schema/src/layer').LayerDocument, 'tags' | 'labels' | 'links' | 'attachments' | 'annotations' | 'extensions'>>;
|
|
4083
|
+
export type LayerExtras = Partial<Pick<import('../../schema/src/layer').LayerDocument, 'tags' | 'labels' | 'links' | 'attachments' | 'annotations' | 'extensions' | 'recordings'>>;
|
|
3987
4084
|
```
|
|
3988
4085
|
|
|
3989
4086
|
What a layer may carry as a whole. All optional and open: see docs/extending.md.
|
|
@@ -5381,6 +5478,69 @@ export function contentTypeForMedia(media: string): string
|
|
|
5381
5478
|
|
|
5382
5479
|
The media type for a snapshot media kind (`html`, `png`, `json`, `text`, `bytes`, `video`); an unknown kind is `application/octet-stream`.
|
|
5383
5480
|
|
|
5481
|
+
### Recording receipts
|
|
5482
|
+
|
|
5483
|
+
Source: `packages/reports/src/recording-receipt.ts`. Import: `@crvouga/atlas/reports/recording-receipt`
|
|
5484
|
+
|
|
5485
|
+
Answers "did the recordings we expected get published?" apart from "did the tests pass?". `recordingReceipt` reads the
|
|
5486
|
+
checks a producer is about to publish (after their files were stored) and describes, per check that declared a
|
|
5487
|
+
`recordingExpectation`, whether its video was uploaded, only referenced, left out (with a reason) or never attached.
|
|
5488
|
+
`recordingCompleteness` reduces the winning results of a report to the figures `atlas report status` prints. Neither
|
|
5489
|
+
reads a clock or the environment, and neither changes a status, a slot or a winner: a receipt describes publication, it
|
|
5490
|
+
does not decide a result. A layer with no receipt is Unknown, never Complete.
|
|
5491
|
+
|
|
5492
|
+
See docs/rich-evidence.md#recording-completeness
|
|
5493
|
+
|
|
5494
|
+
#### `isVideoAttachment`
|
|
5495
|
+
|
|
5496
|
+
```ts
|
|
5497
|
+
export function isVideoAttachment(attachment: Loose): boolean
|
|
5498
|
+
```
|
|
5499
|
+
|
|
5500
|
+
Is this attachment a screen recording: a role of `video`, or a `video/*` media type?
|
|
5501
|
+
|
|
5502
|
+
#### `recordingReceipt`
|
|
5503
|
+
|
|
5504
|
+
```ts
|
|
5505
|
+
export function recordingReceipt(checks: readonly Loose[]): RecordingReceipt | undefined
|
|
5506
|
+
```
|
|
5507
|
+
|
|
5508
|
+
The receipt for the checks of one layer, or `undefined` when no check declares a `recordingExpectation` (the layer then
|
|
5509
|
+
claims nothing). Call it with checks whose attachment bytes are already stored (`bundleKey`, `bytes` and `sha256` set).
|
|
5510
|
+
|
|
5511
|
+
A `required` check is `uploaded` when any video of it has stored bytes, `referenced` when one has only a `uri`,
|
|
5512
|
+
`skipped` when its videos were left out (the attachment's `omitted` is the reason) and `unresolved` when it has no
|
|
5513
|
+
video at all. A `not-applicable` check is listed but is neither expected nor missing. Items are sorted by check id so the
|
|
5514
|
+
same input is always the same receipt.
|
|
5515
|
+
|
|
5516
|
+
#### `RecordingResult`
|
|
5517
|
+
|
|
5518
|
+
```ts
|
|
5519
|
+
export type RecordingResult = {
|
|
5520
|
+
readonly spec: string | null;
|
|
5521
|
+
readonly check: string;
|
|
5522
|
+
readonly environment: string | null;
|
|
5523
|
+
readonly layer: string;
|
|
5524
|
+
/** Whether the layer the result came from carried a receipt. */
|
|
5525
|
+
readonly hasReceipt: boolean;
|
|
5526
|
+
/** The result's item in that receipt, when its check declared an expectation. */
|
|
5527
|
+
readonly recording?: { readonly state: string; readonly reason?: string };
|
|
5528
|
+
};
|
|
5529
|
+
```
|
|
5530
|
+
|
|
5531
|
+
One result as {@link recordingCompleteness} reads it.
|
|
5532
|
+
|
|
5533
|
+
#### `recordingCompleteness`
|
|
5534
|
+
|
|
5535
|
+
```ts
|
|
5536
|
+
export function recordingCompleteness(results: readonly RecordingResult[]): RecordingCompleteness
|
|
5537
|
+
```
|
|
5538
|
+
|
|
5539
|
+
Reduces the winning results of a report to its recording completeness. `incomplete` when any expected recording is
|
|
5540
|
+
skipped or unresolved; otherwise `unknown` when a contributing layer has no receipt (nothing is claimed for it, and a
|
|
5541
|
+
report with no results at all is unknown too); otherwise `complete` when something was expected and `none-expected` when
|
|
5542
|
+
nothing was. Gaps are sorted so the output never depends on read order.
|
|
5543
|
+
|
|
5384
5544
|
### Remote commit report
|
|
5385
5545
|
|
|
5386
5546
|
Source: `packages/reports/src/remote-report.ts`. Import: `@crvouga/atlas/reports/remote-report`
|
|
@@ -5603,6 +5763,7 @@ It reads only immutable layers (`layers/index/…`), which is what makes "read o
|
|
|
5603
5763
|
- `json(): { readonly version: string; readonly text: string }` — The report as JSON text: exactly what `JSON.stringify({ ...snapshot(), active: [] })` writes, put together from the text of each layer, which is written once. `active` is what a store without live runs always sends: none.
|
|
5604
5764
|
- `delta(since: string): { readonly text: string; readonly version: string } | null` — What differs between the report a viewer holds (named by its version) and the current one: the layers that changed in full, and the ids of those that no longer hold a winner. Null when that version is not remembered, and the viewer should ask for the whole report. See `applyEffectiveDelta`.
|
|
5605
5765
|
- `get history(): TestHistory { return this.#history; }` — Every recorded result of every test and every run, as layers were folded in: what the test catalog, a test's history and the run list are built from. Read-only for callers; `history.rev` changes whenever a layer is taken in.
|
|
5766
|
+
- `superseded(): ReadonlySet<string>` — The ids of layers this index has read and that no longer decide any result. Removing one from its store cannot change the report (every slot it filled is held by a newer layer), which is what makes it safe for garbage collection to delete. A layer that failed to load, is hidden by `include`, or still holds a result is not here.
|
|
5606
5767
|
- `stats()` — For `/readyz` and logs.
|
|
5607
5768
|
|
|
5608
5769
|
### Test loading state
|
|
@@ -5756,6 +5917,8 @@ checks (or none completed), failed checks, running checks, specs without results
|
|
|
5756
5917
|
each required environment with no checks (the name is normalised, so `iOS` means `ios`), then every notice of the
|
|
5757
5918
|
report (an unreadable layer or backend). `failing` lists each failed or running winner, in a fixed order that does
|
|
5758
5919
|
not depend on read order; diagnostic `performance` checks are left out unless nothing else was recorded.
|
|
5920
|
+
`recordings` reports recording completeness of the same results apart from `ok`: an incomplete or unknown
|
|
5921
|
+
completeness never changes the gate (use `atlas report publish --require-recordings` to fail on a missing video).
|
|
5759
5922
|
|
|
5760
5923
|
**Example**
|
|
5761
5924
|
|