@crvouga/atlas 0.17.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 +6 -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/garbage.d.ts +39 -0
- package/dist/reports/src/garbage.js +82 -0
- package/dist/reports/src/report-index.d.ts +6 -0
- package/dist/reports/src/report-index.js +14 -0
- package/dist/schema/src/index.d.ts +1 -0
- package/dist/schema/src/index.js +1 -0
- 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 +12 -7
- package/docs/reference/http-api.md +30 -2
- package/docs/reference/reports.md +71 -0
- package/docs/reference/schema.md +102 -16
- package/docs/reference/server.md +263 -41
- package/docs/report-backends.md +0 -144
- package/docs/report-contract.md +1 -1
- 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 +231 -234
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/packages/reports/STORAGE.md +1 -1
- 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/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 +510 -126
- package/packages/visualizer/src/admin/SettingsPage.tsx +183 -88
package/llms.txt
CHANGED
|
@@ -21,7 +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
|
-
- [
|
|
24
|
+
- [docs/report-backends.md](https://raw.githubusercontent.com/crvouga/atlas/main/docs/report-backends.md):
|
|
25
25
|
- [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
26
|
|
|
27
27
|
## Working with this project
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crvouga/atlas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.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",
|
|
@@ -164,7 +164,7 @@
|
|
|
164
164
|
"schemas": "bun packages/schema/scripts/emit-json-schemas.ts",
|
|
165
165
|
"docs": "bun scripts/docs/generate.ts",
|
|
166
166
|
"docs:check": "bun test scripts/docs scripts/release/docs-links.test.ts packages/schema/tests/schemas.test.ts packages/server/tests/openapi.test.ts packages/server/tests/settings.test.ts",
|
|
167
|
-
"dev": "bun packages/core/bin/atlas.mjs dev
|
|
167
|
+
"dev": "bun packages/core/bin/atlas.mjs dev",
|
|
168
168
|
"client:dev": "bun packages/core/bin/atlas.mjs dev",
|
|
169
169
|
"client:build": "bun packages/core/bin/atlas.mjs client build --no-data",
|
|
170
170
|
"client:publish-data": "bun packages/visualizer/scripts/publish-data.ts",
|
|
@@ -69,7 +69,7 @@ adapter records each part independently.
|
|
|
69
69
|
|
|
70
70
|
The existing selected-attempt pointer, reusable content hashes and frame bundles
|
|
71
71
|
continue to own test-run publication. An empty closed run is `skipped`, never a
|
|
72
|
-
successful result inferred from absent evidence. Retention keeps historical evidence and its referenced content, including previous attempts and deleted branches. `pruneReports` reports retained commits and deletes nothing; storage usage grows with unique evidence.
|
|
72
|
+
successful result inferred from absent evidence. Retention keeps historical evidence and its referenced content, including previous attempts and deleted branches. `pruneReports` reports retained commits and deletes nothing; storage usage grows with unique evidence unless an operator saves a garbage collection policy for the server, which deletes only superseded layers (`garbage.ts`, [the server](../../docs/server.md#user-state-and-garbage-collection)).
|
|
73
73
|
|
|
74
74
|
Large bundles overlap at most four 4 MiB chunk uploads per source. Entry parts
|
|
75
75
|
retain input order even when uploads complete out of order. Backpressure bounds
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "https://github.com/crvouga/atlas/schemas/gc-policy.schema.json",
|
|
3
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"enabled": {
|
|
7
|
+
"type": "boolean",
|
|
8
|
+
"description": "Delete eligible layers on a schedule. `false` keeps the policy but deletes nothing"
|
|
9
|
+
},
|
|
10
|
+
"maxAgeDays": {
|
|
11
|
+
"type": "integer",
|
|
12
|
+
"minimum": 1,
|
|
13
|
+
"maximum": 3650,
|
|
14
|
+
"description": "A superseded layer is eligible once it was recorded more than this many days ago"
|
|
15
|
+
},
|
|
16
|
+
"keepLatest": {
|
|
17
|
+
"default": 20,
|
|
18
|
+
"description": "Always keep this many of the newest layers, however old, so recent history stays",
|
|
19
|
+
"type": "integer",
|
|
20
|
+
"minimum": 0,
|
|
21
|
+
"maximum": 1000000
|
|
22
|
+
},
|
|
23
|
+
"intervalHours": {
|
|
24
|
+
"default": 24,
|
|
25
|
+
"description": "How often the server applies the policy",
|
|
26
|
+
"type": "number",
|
|
27
|
+
"minimum": 1,
|
|
28
|
+
"maximum": 720
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"required": [
|
|
32
|
+
"enabled",
|
|
33
|
+
"maxAgeDays"
|
|
34
|
+
],
|
|
35
|
+
"additionalProperties": false
|
|
36
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "https://github.com/crvouga/atlas/schemas/gc-run-result.schema.json",
|
|
3
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"runs": {
|
|
7
|
+
"type": "array",
|
|
8
|
+
"items": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"project": {
|
|
12
|
+
"type": "string"
|
|
13
|
+
},
|
|
14
|
+
"dryRun": {
|
|
15
|
+
"type": "boolean"
|
|
16
|
+
},
|
|
17
|
+
"at": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "When the pass finished, ISO 8601"
|
|
20
|
+
},
|
|
21
|
+
"candidates": {
|
|
22
|
+
"type": "integer",
|
|
23
|
+
"minimum": 0,
|
|
24
|
+
"maximum": 9007199254740991,
|
|
25
|
+
"description": "Layers the policy would delete"
|
|
26
|
+
},
|
|
27
|
+
"deletedLayers": {
|
|
28
|
+
"type": "integer",
|
|
29
|
+
"minimum": 0,
|
|
30
|
+
"maximum": 9007199254740991
|
|
31
|
+
},
|
|
32
|
+
"deletedBlobs": {
|
|
33
|
+
"type": "integer",
|
|
34
|
+
"minimum": 0,
|
|
35
|
+
"maximum": 9007199254740991
|
|
36
|
+
},
|
|
37
|
+
"keptLayers": {
|
|
38
|
+
"type": "integer",
|
|
39
|
+
"minimum": 0,
|
|
40
|
+
"maximum": 9007199254740991
|
|
41
|
+
},
|
|
42
|
+
"error": {
|
|
43
|
+
"description": "Why the pass stopped early, when it did",
|
|
44
|
+
"type": "string"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"required": [
|
|
48
|
+
"project",
|
|
49
|
+
"dryRun",
|
|
50
|
+
"at",
|
|
51
|
+
"candidates",
|
|
52
|
+
"deletedLayers",
|
|
53
|
+
"deletedBlobs",
|
|
54
|
+
"keptLayers"
|
|
55
|
+
],
|
|
56
|
+
"additionalProperties": {}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"policy": {
|
|
60
|
+
"anyOf": [
|
|
61
|
+
{
|
|
62
|
+
"type": "object",
|
|
63
|
+
"properties": {
|
|
64
|
+
"enabled": {
|
|
65
|
+
"type": "boolean",
|
|
66
|
+
"description": "Delete eligible layers on a schedule. `false` keeps the policy but deletes nothing"
|
|
67
|
+
},
|
|
68
|
+
"maxAgeDays": {
|
|
69
|
+
"type": "integer",
|
|
70
|
+
"minimum": 1,
|
|
71
|
+
"maximum": 3650,
|
|
72
|
+
"description": "A superseded layer is eligible once it was recorded more than this many days ago"
|
|
73
|
+
},
|
|
74
|
+
"keepLatest": {
|
|
75
|
+
"default": 20,
|
|
76
|
+
"description": "Always keep this many of the newest layers, however old, so recent history stays",
|
|
77
|
+
"type": "integer",
|
|
78
|
+
"minimum": 0,
|
|
79
|
+
"maximum": 1000000
|
|
80
|
+
},
|
|
81
|
+
"intervalHours": {
|
|
82
|
+
"default": 24,
|
|
83
|
+
"description": "How often the server applies the policy",
|
|
84
|
+
"type": "number",
|
|
85
|
+
"minimum": 1,
|
|
86
|
+
"maximum": 720
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"required": [
|
|
90
|
+
"enabled",
|
|
91
|
+
"maxAgeDays"
|
|
92
|
+
],
|
|
93
|
+
"additionalProperties": false
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"type": "null"
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"required": [
|
|
102
|
+
"runs",
|
|
103
|
+
"policy"
|
|
104
|
+
],
|
|
105
|
+
"additionalProperties": {}
|
|
106
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "https://github.com/crvouga/atlas/schemas/gc-snapshot.schema.json",
|
|
3
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"revision": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
9
|
+
},
|
|
10
|
+
"policy": {
|
|
11
|
+
"anyOf": [
|
|
12
|
+
{
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"enabled": {
|
|
16
|
+
"type": "boolean",
|
|
17
|
+
"description": "Delete eligible layers on a schedule. `false` keeps the policy but deletes nothing"
|
|
18
|
+
},
|
|
19
|
+
"maxAgeDays": {
|
|
20
|
+
"type": "integer",
|
|
21
|
+
"minimum": 1,
|
|
22
|
+
"maximum": 3650,
|
|
23
|
+
"description": "A superseded layer is eligible once it was recorded more than this many days ago"
|
|
24
|
+
},
|
|
25
|
+
"keepLatest": {
|
|
26
|
+
"default": 20,
|
|
27
|
+
"description": "Always keep this many of the newest layers, however old, so recent history stays",
|
|
28
|
+
"type": "integer",
|
|
29
|
+
"minimum": 0,
|
|
30
|
+
"maximum": 1000000
|
|
31
|
+
},
|
|
32
|
+
"intervalHours": {
|
|
33
|
+
"default": 24,
|
|
34
|
+
"description": "How often the server applies the policy",
|
|
35
|
+
"type": "number",
|
|
36
|
+
"minimum": 1,
|
|
37
|
+
"maximum": 720
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"required": [
|
|
41
|
+
"enabled",
|
|
42
|
+
"maxAgeDays"
|
|
43
|
+
],
|
|
44
|
+
"additionalProperties": false
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"type": "null"
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"saved": {
|
|
52
|
+
"type": "boolean",
|
|
53
|
+
"description": "Whether a policy exists in S3. `false` means garbage collection is off: nothing is deleted"
|
|
54
|
+
},
|
|
55
|
+
"lastRuns": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"items": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"properties": {
|
|
60
|
+
"project": {
|
|
61
|
+
"type": "string"
|
|
62
|
+
},
|
|
63
|
+
"dryRun": {
|
|
64
|
+
"type": "boolean"
|
|
65
|
+
},
|
|
66
|
+
"at": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"description": "When the pass finished, ISO 8601"
|
|
69
|
+
},
|
|
70
|
+
"candidates": {
|
|
71
|
+
"type": "integer",
|
|
72
|
+
"minimum": 0,
|
|
73
|
+
"maximum": 9007199254740991,
|
|
74
|
+
"description": "Layers the policy would delete"
|
|
75
|
+
},
|
|
76
|
+
"deletedLayers": {
|
|
77
|
+
"type": "integer",
|
|
78
|
+
"minimum": 0,
|
|
79
|
+
"maximum": 9007199254740991
|
|
80
|
+
},
|
|
81
|
+
"deletedBlobs": {
|
|
82
|
+
"type": "integer",
|
|
83
|
+
"minimum": 0,
|
|
84
|
+
"maximum": 9007199254740991
|
|
85
|
+
},
|
|
86
|
+
"keptLayers": {
|
|
87
|
+
"type": "integer",
|
|
88
|
+
"minimum": 0,
|
|
89
|
+
"maximum": 9007199254740991
|
|
90
|
+
},
|
|
91
|
+
"error": {
|
|
92
|
+
"description": "Why the pass stopped early, when it did",
|
|
93
|
+
"type": "string"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"required": [
|
|
97
|
+
"project",
|
|
98
|
+
"dryRun",
|
|
99
|
+
"at",
|
|
100
|
+
"candidates",
|
|
101
|
+
"deletedLayers",
|
|
102
|
+
"deletedBlobs",
|
|
103
|
+
"keptLayers"
|
|
104
|
+
],
|
|
105
|
+
"additionalProperties": {}
|
|
106
|
+
},
|
|
107
|
+
"description": "The most recent pass over each project, newest first; empty before the first pass"
|
|
108
|
+
},
|
|
109
|
+
"stateKey": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"description": "Where the user state lives in the bucket, relative to the configured prefix"
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"required": [
|
|
115
|
+
"revision",
|
|
116
|
+
"policy",
|
|
117
|
+
"saved",
|
|
118
|
+
"lastRuns",
|
|
119
|
+
"stateKey"
|
|
120
|
+
],
|
|
121
|
+
"additionalProperties": {}
|
|
122
|
+
}
|
|
@@ -17,47 +17,6 @@
|
|
|
17
17
|
"minimum": 0,
|
|
18
18
|
"maximum": 65535
|
|
19
19
|
},
|
|
20
|
-
"data": {
|
|
21
|
-
"default": "./atlas-data",
|
|
22
|
-
"description": "The folder that is the default backend and the default write target: layers, specs and runs live here",
|
|
23
|
-
"type": "string"
|
|
24
|
-
},
|
|
25
|
-
"backends": {
|
|
26
|
-
"default": [],
|
|
27
|
-
"description": "Further backends, read together with the data folder: S3, another server, a device lab",
|
|
28
|
-
"type": "array",
|
|
29
|
-
"items": {
|
|
30
|
-
"type": "object",
|
|
31
|
-
"properties": {
|
|
32
|
-
"id": {
|
|
33
|
-
"type": "string",
|
|
34
|
-
"pattern": "^[a-z0-9-]+$",
|
|
35
|
-
"description": "Lowercase letters, digits and hyphens. Names where a result came from"
|
|
36
|
-
},
|
|
37
|
-
"label": {
|
|
38
|
-
"type": "string"
|
|
39
|
-
},
|
|
40
|
-
"url": {
|
|
41
|
-
"type": "string",
|
|
42
|
-
"minLength": 1,
|
|
43
|
-
"description": "A folder, s3://bucket/prefix, r2://, gs://, or another Atlas server (https://…)"
|
|
44
|
-
},
|
|
45
|
-
"write": {
|
|
46
|
-
"description": "Uploads go here instead of the data folder. At most one backend is the write target",
|
|
47
|
-
"type": "boolean"
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
"required": [
|
|
51
|
-
"id",
|
|
52
|
-
"url"
|
|
53
|
-
],
|
|
54
|
-
"additionalProperties": false
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
"write": {
|
|
58
|
-
"description": "The id of the backend uploads go to. Default: the data folder",
|
|
59
|
-
"type": "string"
|
|
60
|
-
},
|
|
61
20
|
"auth": {
|
|
62
21
|
"default": {
|
|
63
22
|
"tokens": [],
|
|
@@ -3,47 +3,6 @@
|
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
6
|
-
"data": {
|
|
7
|
-
"default": "./atlas-data",
|
|
8
|
-
"description": "The folder that is the default backend and the default write target: layers, specs and runs live here",
|
|
9
|
-
"type": "string"
|
|
10
|
-
},
|
|
11
|
-
"backends": {
|
|
12
|
-
"default": [],
|
|
13
|
-
"description": "Further backends, read together with the data folder: S3, another server, a device lab",
|
|
14
|
-
"type": "array",
|
|
15
|
-
"items": {
|
|
16
|
-
"type": "object",
|
|
17
|
-
"properties": {
|
|
18
|
-
"id": {
|
|
19
|
-
"type": "string",
|
|
20
|
-
"pattern": "^[a-z0-9-]+$",
|
|
21
|
-
"description": "Lowercase letters, digits and hyphens. Names where a result came from"
|
|
22
|
-
},
|
|
23
|
-
"label": {
|
|
24
|
-
"type": "string"
|
|
25
|
-
},
|
|
26
|
-
"url": {
|
|
27
|
-
"type": "string",
|
|
28
|
-
"minLength": 1,
|
|
29
|
-
"description": "A folder, s3://bucket/prefix, r2://, gs://, or another Atlas server (https://…)"
|
|
30
|
-
},
|
|
31
|
-
"write": {
|
|
32
|
-
"description": "Uploads go here instead of the data folder. At most one backend is the write target",
|
|
33
|
-
"type": "boolean"
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
"required": [
|
|
37
|
-
"id",
|
|
38
|
-
"url"
|
|
39
|
-
],
|
|
40
|
-
"additionalProperties": false
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
"write": {
|
|
44
|
-
"description": "The id of the backend uploads go to. Default: the data folder",
|
|
45
|
-
"type": "string"
|
|
46
|
-
},
|
|
47
6
|
"outlineBranch": {
|
|
48
7
|
"description": "Which branch's specs the report is laid over. Default main, then master, then the newest",
|
|
49
8
|
"type": "string"
|
|
@@ -10,47 +10,6 @@
|
|
|
10
10
|
"settings": {
|
|
11
11
|
"type": "object",
|
|
12
12
|
"properties": {
|
|
13
|
-
"data": {
|
|
14
|
-
"default": "./atlas-data",
|
|
15
|
-
"description": "The folder that is the default backend and the default write target: layers, specs and runs live here",
|
|
16
|
-
"type": "string"
|
|
17
|
-
},
|
|
18
|
-
"backends": {
|
|
19
|
-
"default": [],
|
|
20
|
-
"description": "Further backends, read together with the data folder: S3, another server, a device lab",
|
|
21
|
-
"type": "array",
|
|
22
|
-
"items": {
|
|
23
|
-
"type": "object",
|
|
24
|
-
"properties": {
|
|
25
|
-
"id": {
|
|
26
|
-
"type": "string",
|
|
27
|
-
"pattern": "^[a-z0-9-]+$",
|
|
28
|
-
"description": "Lowercase letters, digits and hyphens. Names where a result came from"
|
|
29
|
-
},
|
|
30
|
-
"label": {
|
|
31
|
-
"type": "string"
|
|
32
|
-
},
|
|
33
|
-
"url": {
|
|
34
|
-
"type": "string",
|
|
35
|
-
"minLength": 1,
|
|
36
|
-
"description": "A folder, s3://bucket/prefix, r2://, gs://, or another Atlas server (https://…)"
|
|
37
|
-
},
|
|
38
|
-
"write": {
|
|
39
|
-
"description": "Uploads go here instead of the data folder. At most one backend is the write target",
|
|
40
|
-
"type": "boolean"
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
"required": [
|
|
44
|
-
"id",
|
|
45
|
-
"url"
|
|
46
|
-
],
|
|
47
|
-
"additionalProperties": false
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
"write": {
|
|
51
|
-
"description": "The id of the backend uploads go to. Default: the data folder",
|
|
52
|
-
"type": "string"
|
|
53
|
-
},
|
|
54
13
|
"outlineBranch": {
|
|
55
14
|
"description": "Which branch's specs the report is laid over. Default main, then master, then the newest",
|
|
56
15
|
"type": "string"
|
|
@@ -140,47 +99,6 @@
|
|
|
140
99
|
"active": {
|
|
141
100
|
"type": "object",
|
|
142
101
|
"properties": {
|
|
143
|
-
"data": {
|
|
144
|
-
"default": "./atlas-data",
|
|
145
|
-
"description": "The folder that is the default backend and the default write target: layers, specs and runs live here",
|
|
146
|
-
"type": "string"
|
|
147
|
-
},
|
|
148
|
-
"backends": {
|
|
149
|
-
"default": [],
|
|
150
|
-
"description": "Further backends, read together with the data folder: S3, another server, a device lab",
|
|
151
|
-
"type": "array",
|
|
152
|
-
"items": {
|
|
153
|
-
"type": "object",
|
|
154
|
-
"properties": {
|
|
155
|
-
"id": {
|
|
156
|
-
"type": "string",
|
|
157
|
-
"pattern": "^[a-z0-9-]+$",
|
|
158
|
-
"description": "Lowercase letters, digits and hyphens. Names where a result came from"
|
|
159
|
-
},
|
|
160
|
-
"label": {
|
|
161
|
-
"type": "string"
|
|
162
|
-
},
|
|
163
|
-
"url": {
|
|
164
|
-
"type": "string",
|
|
165
|
-
"minLength": 1,
|
|
166
|
-
"description": "A folder, s3://bucket/prefix, r2://, gs://, or another Atlas server (https://…)"
|
|
167
|
-
},
|
|
168
|
-
"write": {
|
|
169
|
-
"description": "Uploads go here instead of the data folder. At most one backend is the write target",
|
|
170
|
-
"type": "boolean"
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
"required": [
|
|
174
|
-
"id",
|
|
175
|
-
"url"
|
|
176
|
-
],
|
|
177
|
-
"additionalProperties": false
|
|
178
|
-
}
|
|
179
|
-
},
|
|
180
|
-
"write": {
|
|
181
|
-
"description": "The id of the backend uploads go to. Default: the data folder",
|
|
182
|
-
"type": "string"
|
|
183
|
-
},
|
|
184
102
|
"outlineBranch": {
|
|
185
103
|
"description": "Which branch's specs the report is laid over. Default main, then master, then the newest",
|
|
186
104
|
"type": "string"
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "https://github.com/crvouga/atlas/schemas/user-state.schema.json",
|
|
3
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"schemaVersion": {
|
|
7
|
+
"type": "number",
|
|
8
|
+
"const": 1
|
|
9
|
+
},
|
|
10
|
+
"gc": {
|
|
11
|
+
"description": "The garbage collection policy. Absent until someone saves one: then nothing is deleted",
|
|
12
|
+
"type": "object",
|
|
13
|
+
"properties": {
|
|
14
|
+
"enabled": {
|
|
15
|
+
"type": "boolean",
|
|
16
|
+
"description": "Delete eligible layers on a schedule. `false` keeps the policy but deletes nothing"
|
|
17
|
+
},
|
|
18
|
+
"maxAgeDays": {
|
|
19
|
+
"type": "integer",
|
|
20
|
+
"minimum": 1,
|
|
21
|
+
"maximum": 3650,
|
|
22
|
+
"description": "A superseded layer is eligible once it was recorded more than this many days ago"
|
|
23
|
+
},
|
|
24
|
+
"keepLatest": {
|
|
25
|
+
"default": 20,
|
|
26
|
+
"description": "Always keep this many of the newest layers, however old, so recent history stays",
|
|
27
|
+
"type": "integer",
|
|
28
|
+
"minimum": 0,
|
|
29
|
+
"maximum": 1000000
|
|
30
|
+
},
|
|
31
|
+
"intervalHours": {
|
|
32
|
+
"default": 24,
|
|
33
|
+
"description": "How often the server applies the policy",
|
|
34
|
+
"type": "number",
|
|
35
|
+
"minimum": 1,
|
|
36
|
+
"maximum": 720
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"required": [
|
|
40
|
+
"enabled",
|
|
41
|
+
"maxAgeDays"
|
|
42
|
+
],
|
|
43
|
+
"additionalProperties": false
|
|
44
|
+
},
|
|
45
|
+
"settings": {
|
|
46
|
+
"description": "Report and live-run settings saved from the settings page, applied at the next start",
|
|
47
|
+
"type": "object",
|
|
48
|
+
"properties": {
|
|
49
|
+
"outlineBranch": {
|
|
50
|
+
"description": "Which branch's specs the report is laid over. Default main, then master, then the newest",
|
|
51
|
+
"type": "string"
|
|
52
|
+
},
|
|
53
|
+
"include": {
|
|
54
|
+
"default": {},
|
|
55
|
+
"type": "object",
|
|
56
|
+
"properties": {
|
|
57
|
+
"branches": {
|
|
58
|
+
"description": "Only layers recorded on these branches take part",
|
|
59
|
+
"type": "array",
|
|
60
|
+
"items": {
|
|
61
|
+
"type": "string"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"ignoreModified": {
|
|
65
|
+
"description": "Leave out layers from modified checkouts and unknown origins",
|
|
66
|
+
"type": "boolean"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"additionalProperties": false
|
|
70
|
+
},
|
|
71
|
+
"refreshSeconds": {
|
|
72
|
+
"default": 10,
|
|
73
|
+
"description": "How often the backends are looked at for layers written by something other than this server",
|
|
74
|
+
"type": "number",
|
|
75
|
+
"minimum": 1
|
|
76
|
+
},
|
|
77
|
+
"limits": {
|
|
78
|
+
"default": {
|
|
79
|
+
"maxObjectMiB": 256,
|
|
80
|
+
"maxJsonMiB": 32,
|
|
81
|
+
"maxStreams": 500
|
|
82
|
+
},
|
|
83
|
+
"type": "object",
|
|
84
|
+
"properties": {
|
|
85
|
+
"maxObjectMiB": {
|
|
86
|
+
"default": 256,
|
|
87
|
+
"description": "The largest object an upload may be",
|
|
88
|
+
"type": "number",
|
|
89
|
+
"exclusiveMinimum": 0
|
|
90
|
+
},
|
|
91
|
+
"maxJsonMiB": {
|
|
92
|
+
"default": 32,
|
|
93
|
+
"description": "The largest JSON body (a manifest, a checks request)",
|
|
94
|
+
"type": "number",
|
|
95
|
+
"exclusiveMinimum": 0
|
|
96
|
+
},
|
|
97
|
+
"maxStreams": {
|
|
98
|
+
"default": 500,
|
|
99
|
+
"description": "Viewers that may be connected for live updates at once",
|
|
100
|
+
"type": "integer",
|
|
101
|
+
"exclusiveMinimum": 0,
|
|
102
|
+
"maximum": 9007199254740991
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"additionalProperties": false
|
|
106
|
+
},
|
|
107
|
+
"heartbeatSeconds": {
|
|
108
|
+
"default": 15,
|
|
109
|
+
"description": "How often a live connection is touched, so proxies keep it open",
|
|
110
|
+
"type": "number",
|
|
111
|
+
"minimum": 1
|
|
112
|
+
},
|
|
113
|
+
"runTimeoutSeconds": {
|
|
114
|
+
"default": 30,
|
|
115
|
+
"description": "Interrupt a live run after this many seconds without a producer heartbeat",
|
|
116
|
+
"type": "number",
|
|
117
|
+
"minimum": 10,
|
|
118
|
+
"maximum": 3600
|
|
119
|
+
},
|
|
120
|
+
"runStallSeconds": {
|
|
121
|
+
"default": 900,
|
|
122
|
+
"description": "Interrupt after this many seconds without test progress; 0 disables progress expiry",
|
|
123
|
+
"type": "number",
|
|
124
|
+
"minimum": 0,
|
|
125
|
+
"maximum": 86400
|
|
126
|
+
},
|
|
127
|
+
"log": {
|
|
128
|
+
"default": true,
|
|
129
|
+
"description": "One JSON line per request",
|
|
130
|
+
"type": "boolean"
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
"additionalProperties": false
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
"required": [
|
|
137
|
+
"schemaVersion"
|
|
138
|
+
],
|
|
139
|
+
"additionalProperties": {}
|
|
140
|
+
}
|
|
@@ -4,17 +4,21 @@ The Atlas server: one process per project that takes in results from every envir
|
|
|
4
4
|
everyone the combined report, live. A Bun API that serves the Vite-built viewer from the same host (and, under
|
|
5
5
|
`atlas dev`, the viewer's dev server proxies `/api` to it, so development is one host too).
|
|
6
6
|
|
|
7
|
-
Use it from the command line: `atlas serve`, `atlas dev` ([the guide](../../docs/server.md)).
|
|
8
|
-
|
|
7
|
+
Use it from the command line: `atlas serve`, `atlas dev` ([the guide](../../docs/server.md)). The command-line server keeps
|
|
8
|
+
**all reports and all user state (the garbage collection policy, saved settings) in S3-compatible storage and nowhere
|
|
9
|
+
else**, set by the required `ATLAS_S3_ENDPOINT`, `ATLAS_S3_BUCKET`, `ATLAS_S3_REGION`, `ATLAS_S3_ACCESS_KEY_ID` and
|
|
10
|
+
`ATLAS_S3_SECRET_ACCESS_KEY` variables (see [`.env.example`](https://github.com/crvouga/atlas/blob/main/.env.example)); it exits with status 2, listing every
|
|
11
|
+
variable and what is wrong, if they are missing or invalid. Use it from code when you want the server inside something else,
|
|
12
|
+
and bring your own `ObjectStore` (a bucket, or a memory store in tests):
|
|
9
13
|
|
|
10
14
|
```ts
|
|
11
15
|
import { createAtlasServer } from '@crvouga/atlas/server';
|
|
12
16
|
import { listen } from '@crvouga/atlas/server';
|
|
13
|
-
import {
|
|
17
|
+
import { s3Store } from '@crvouga/atlas/reports/s3-store';
|
|
14
18
|
|
|
15
19
|
const server = createAtlasServer({
|
|
16
|
-
backends: [{ id: '
|
|
17
|
-
write: '
|
|
20
|
+
backends: [{ id: 's3', store: s3Store({ bucket: 'my-atlas-reports', region: 'eu-west-1' }) }],
|
|
21
|
+
write: 's3', // where uploads go
|
|
18
22
|
tokens: [{ name: 'ci', role: 'publish', token: process.env.ATLAS_PUBLISH_TOKEN! }],
|
|
19
23
|
site: './atlas-site' // the built viewer; without it only the API is served
|
|
20
24
|
});
|
|
@@ -33,7 +37,8 @@ to viewers, `server.close()` stops both.
|
|
|
33
37
|
| `store-api`, `validate` | The upload protocol and what it refuses (content-addressed layers, dependency order, immutability). |
|
|
34
38
|
| `events` | `EventHub`: server-sent events to every viewer, with a heartbeat and a backlog limit. |
|
|
35
39
|
| `auth` | Roles (`read`, `publish`, `admin`), Bearer tokens, constant-time comparison. |
|
|
36
|
-
| `config` | `loadServerConfig`: the file, then the environment, then the flags; every mistake said in words. |
|
|
40
|
+
| `config`, `storage` | `loadServerConfig`: the saved settings, the file, then the environment, then the flags; every mistake said in words. `resolveS3Config`: the required `ATLAS_S3_*` variables, all problems reported together. |
|
|
41
|
+
| `user-state`, `settings` | The user state JSON in S3 (`server/state.json`): the garbage collection policy and saved settings. |
|
|
37
42
|
| `main`, `dev` | `atlas serve` and `atlas dev`. |
|
|
38
43
|
|
|
39
44
|
The server describes itself: `GET /api/openapi.json` is an OpenAPI 3.1 document of every route (also `@crvouga/atlas/openapi.json`),
|