@crvouga/atlas 0.5.0 → 0.6.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.
Files changed (140) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +30 -11
  3. package/dist/core/src/cli.d.ts +1 -1
  4. package/dist/core/src/cli.js +8 -3
  5. package/dist/core/src/config.d.ts +7 -0
  6. package/dist/core/src/config.js +48 -14
  7. package/dist/core/src/run/execute.d.ts +6 -0
  8. package/dist/core/src/run/execute.js +4 -2
  9. package/dist/reports/src/doctor.d.ts +1 -1
  10. package/dist/reports/src/doctor.js +3 -1
  11. package/dist/reports/src/http-store.d.ts +4 -0
  12. package/dist/reports/src/http-store.js +6 -3
  13. package/dist/reports/src/init.js +6 -1
  14. package/dist/reports/src/live-reporter.d.ts +34 -0
  15. package/dist/reports/src/live-reporter.js +122 -0
  16. package/dist/reports/src/report-cli.d.ts +1 -1
  17. package/dist/reports/src/report-cli.js +3 -2
  18. package/dist/reports/src/store-url.js +1 -1
  19. package/dist/runners/src/codecept.d.ts +14 -0
  20. package/dist/runners/src/codecept.js +16 -0
  21. package/dist/runners/src/jest.d.ts +50 -0
  22. package/dist/runners/src/jest.js +52 -0
  23. package/dist/runners/src/mocha.d.ts +40 -0
  24. package/dist/runners/src/mocha.js +39 -0
  25. package/dist/runners/src/node-test.d.ts +28 -0
  26. package/dist/runners/src/node-test.js +44 -0
  27. package/dist/runners/src/publish.d.ts +30 -0
  28. package/dist/runners/src/publish.js +68 -0
  29. package/dist/schema/src/api.d.ts +1 -0
  30. package/dist/schema/src/api.js +1 -1
  31. package/dist/schema/src/index.d.ts +2 -0
  32. package/dist/schema/src/index.js +2 -0
  33. package/dist/schema/src/live-run.d.ts +123 -0
  34. package/dist/schema/src/live-run.js +30 -0
  35. package/dist/schema/src/manifest.d.ts +2 -2
  36. package/dist/schema/src/project.d.ts +20 -0
  37. package/dist/schema/src/project.js +10 -0
  38. package/dist/schema/src/report-sources.d.ts +1 -0
  39. package/dist/schema/src/report-sources.js +2 -1
  40. package/dist/schema/src/runs-index.d.ts +1 -1
  41. package/dist/schema/src/server-config.d.ts +84 -0
  42. package/dist/schema/src/server-config.js +12 -0
  43. package/dist/server/src/app.d.ts +10 -0
  44. package/dist/server/src/app.js +148 -10
  45. package/dist/server/src/config.d.ts +1 -1
  46. package/dist/server/src/config.js +20 -3
  47. package/dist/server/src/dev.d.ts +3 -3
  48. package/dist/server/src/dev.js +22 -13
  49. package/dist/server/src/events.d.ts +3 -3
  50. package/dist/server/src/events.js +1 -1
  51. package/dist/server/src/index.d.ts +2 -0
  52. package/dist/server/src/index.js +2 -0
  53. package/dist/server/src/live-runs.d.ts +52 -0
  54. package/dist/server/src/live-runs.js +229 -0
  55. package/dist/server/src/main.d.ts +1 -1
  56. package/dist/server/src/main.js +7 -5
  57. package/dist/server/src/openapi.d.ts +2 -5
  58. package/dist/server/src/openapi.js +71 -7
  59. package/dist/server/src/projects.d.ts +3 -0
  60. package/dist/server/src/projects.js +154 -0
  61. package/dist/server/src/settings-page.d.ts +9 -0
  62. package/dist/server/src/settings-page.js +93 -0
  63. package/dist/server/src/settings.d.ts +23 -0
  64. package/dist/server/src/settings.js +72 -0
  65. package/dist/server/src/site.d.ts +8 -6
  66. package/dist/server/src/site.js +45 -10
  67. package/dist/server/src/store-api.js +43 -22
  68. package/docs/deploying.md +19 -19
  69. package/docs/dogfooding.md +2 -2
  70. package/docs/extending.md +3 -3
  71. package/docs/integrating.md +9 -9
  72. package/docs/reference/cli.md +9 -7
  73. package/docs/reference/core.md +6 -0
  74. package/docs/reference/http-api.md +498 -2
  75. package/docs/reference/reports.md +55 -1
  76. package/docs/reference/runners.md +169 -0
  77. package/docs/reference/schema.md +111 -0
  78. package/docs/reference/server.md +154 -14
  79. package/docs/report-backends.md +8 -8
  80. package/docs/report-contract.md +7 -7
  81. package/docs/reporting-issues.md +3 -3
  82. package/docs/runners.md +69 -0
  83. package/docs/server.md +61 -25
  84. package/docs/statecharts.md +22 -20
  85. package/llms-full.txt +713 -105
  86. package/llms.txt +3 -1
  87. package/package.json +31 -9
  88. package/packages/runners/README.md +6 -0
  89. package/packages/runners/compat/codecept.cjs +2 -0
  90. package/packages/runners/compat/jest.cjs +2 -0
  91. package/packages/runners/compat/mocha.cjs +2 -0
  92. package/packages/schema/schemas/atlas-change.schema.json +4 -0
  93. package/packages/schema/schemas/hello-event.schema.json +4 -0
  94. package/packages/schema/schemas/live-run-start.schema.json +42 -0
  95. package/packages/schema/schemas/live-run-update.schema.json +93 -0
  96. package/packages/schema/schemas/live-run.schema.json +225 -0
  97. package/packages/schema/schemas/project-slug.schema.json +8 -0
  98. package/packages/schema/schemas/project.schema.json +22 -0
  99. package/packages/schema/schemas/projects.schema.json +33 -0
  100. package/packages/schema/schemas/server-config.schema.json +14 -0
  101. package/packages/schema/schemas/server-editable-settings.schema.json +132 -0
  102. package/packages/schema/schemas/server-settings-snapshot.schema.json +285 -0
  103. package/packages/server/openapi.json +5101 -779
  104. package/packages/visualizer/README.md +13 -13
  105. package/packages/visualizer/bin/atlas-client.mjs +176 -0
  106. package/packages/visualizer/bin/atlas-visualizer.mjs +2 -175
  107. package/packages/visualizer/src/app/AppShell.tsx +26 -17
  108. package/packages/visualizer/src/app/report-sources.tsx +2 -1
  109. package/packages/visualizer/src/components/ActivityIndicator.module.css +4 -0
  110. package/packages/visualizer/src/components/ActivityIndicator.tsx +6 -0
  111. package/packages/visualizer/src/components/Header.module.css +101 -227
  112. package/packages/visualizer/src/components/Header.tsx +79 -41
  113. package/packages/visualizer/src/components/ProjectPicker.tsx +64 -0
  114. package/packages/visualizer/src/data/adapters/report-backend.test.ts +3 -2
  115. package/packages/visualizer/src/data/adapters/report-backend.ts +1 -1
  116. package/packages/visualizer/src/data/queries.ts +14 -7
  117. package/packages/visualizer/src/evidence/api.ts +2 -1
  118. package/packages/visualizer/src/evidence/check-detail.tsx +19 -4
  119. package/packages/visualizer/src/evidence/document-page.tsx +6 -2
  120. package/packages/visualizer/src/evidence/evidence-context.tsx +17 -12
  121. package/packages/visualizer/src/evidence/evidence.module.css +11 -1
  122. package/packages/visualizer/src/evidence/feature-page.tsx +7 -3
  123. package/packages/visualizer/src/evidence/model-page.tsx +6 -2
  124. package/packages/visualizer/src/evidence/project-home.tsx +45 -0
  125. package/packages/visualizer/src/evidence/recording-page.tsx +4 -1
  126. package/packages/visualizer/src/evidence/spec-index.tsx +2 -1
  127. package/packages/visualizer/src/evidence/test-filter.tsx +21 -0
  128. package/packages/visualizer/src/live/live-query.ts +21 -0
  129. package/packages/visualizer/src/live/live-runs.tsx +69 -0
  130. package/packages/visualizer/src/live/live-stream.ts +30 -9
  131. package/packages/visualizer/src/live/report-progress.ts +62 -0
  132. package/packages/visualizer/src/live/test-progress.tsx +34 -0
  133. package/packages/visualizer/src/runtime.ts +17 -1
  134. package/packages/visualizer/src/state/ui-store.ts +3 -2
  135. package/packages/visualizer/src/views/ChartPage.tsx +2 -0
  136. package/packages/visualizer/src/views/ChartTests.tsx +43 -0
  137. package/packages/visualizer/src/views/SidePanel.tsx +2 -0
  138. package/packages/visualizer/vite/atlas-files.test.ts +4 -4
  139. package/packages/visualizer/vite/atlas-files.ts +1 -5
  140. package/packages/visualizer/vite/atlas-server-proxy.ts +2 -1
@@ -895,7 +895,7 @@ One thing `inspectStore` checked: its `name` (`store`, `list`, `contents`, `writ
895
895
  export function iamPolicy(location: { bucket: string; prefix: string }, role: 'publisher' | 'reader' | 'both'): object
896
896
  ```
897
897
 
898
- The least an account needs, as an AWS IAM policy. R2, MinIO and the rest have their own policy languages: grant the same actions.
898
+ The least an account needs, as an AWS IAM policy. The combined server role also stores operational leases under server/live/. R2, MinIO and the rest have their own policy languages: grant the same actions.
899
899
 
900
900
  #### `Role`
901
901
 
@@ -2453,6 +2453,8 @@ See docs/server.md#the-store-protocol
2453
2453
 
2454
2454
  ```ts
2455
2455
  export type HttpStoreOptions = {
2456
+ /** Project namespace on a shared Atlas node. Omit for the legacy default project. */
2457
+ readonly project?: string;
2456
2458
  /** The Atlas server, such as `https://atlas.example.com`. A path on it (`/atlas`) is kept. */
2457
2459
  readonly url: string;
2458
2460
  /** A publish or admin token to write with, a read token to read with. Never put it in the URL. */
@@ -2480,6 +2482,8 @@ What an Atlas server said no to, with what to do about it.
2480
2482
 
2481
2483
  ```ts
2482
2484
  export type HttpStore = ObjectStore & PagedListing & {
2485
+ /** The same authenticated node connection, scoped to another project. */
2486
+ forProject(project: string): HttpStore;
2483
2487
  /** `https://atlas.example.com`, with no secrets. */
2484
2488
  readonly label: string;
2485
2489
  readonly url: string;
@@ -3619,6 +3623,56 @@ Routes, relative to where it is mounted:
3619
3623
  Directory listings are cached for 30 s (1.5 s for the local store). A listing that fails keeps the previous keys and
3620
3624
  marks the backend unavailable; the page then carries a notice.
3621
3625
 
3626
+ ### live-reporter.ts
3627
+
3628
+ Source: `packages/reports/src/live-reporter.ts`. Import: `@crvouga/atlas/reports/live-reporter`
3629
+
3630
+ @module Reporting a live run to Atlas
3631
+ Streams bounded check deltas while tests execute. Heartbeats are independent of progress; finished results are
3632
+ published by the server as ordinary immutable layers. A final flush is awaited before a runner exits.
3633
+
3634
+ #### `LiveIdentity`
3635
+
3636
+ ```ts
3637
+ export type LiveIdentity = Omit<z.input<typeof LiveRunStartSchema>, 'id'> & { id?: string };
3638
+ ```
3639
+
3640
+ Fixed identity of a live invocation; a UUID is generated when omitted.
3641
+
3642
+ #### `LiveReporterOptions`
3643
+
3644
+ ```ts
3645
+ export type LiveReporterOptions = { url: string; project?: string; token?: string; flushMs?: number; heartbeatMs?: number; timeoutMs?: number; onError?: (error: Error) => void };
3646
+ ```
3647
+
3648
+ Transport and batching configuration. Defaults: 250 ms batches, 5 s heartbeats and 10 s request timeouts.
3649
+
3650
+ #### `LiveReporter`
3651
+
3652
+ ```ts
3653
+ export type LiveReporter = { readonly id: string; readonly finishedAtMs?: number; check(check: LiveCheck): void; flush(): Promise<void>; finish(status?: 'completed' | 'interrupted', reason?: string): Promise<void> };
3654
+ ```
3655
+
3656
+ Producer handle. check never throws transport errors into a test; finish flushes and rejects if publication failed.
3657
+
3658
+ #### `createLiveReporter`
3659
+
3660
+ ```ts
3661
+ export function createLiveReporter(identity: LiveIdentity, options: LiveReporterOptions): LiveReporter
3662
+ ```
3663
+
3664
+ Start publishing immediately, batch changing checks, and send independent heartbeats while the producer is alive.
3665
+ The same failed batch is retried with the same sequence and content; never silently replace a lost update.
3666
+
3667
+ **Example**
3668
+
3669
+ ```ts
3670
+ const run = createLiveReporter({ producer: 'unit', plannedChecks: 10 }, { url: 'http://localhost:5180' });
3671
+ run.check({ id: 'a', title: 'Adds a todo', status: 'running' });
3672
+ run.check({ id: 'a', title: 'Adds a todo', status: 'passed' });
3673
+ await run.finish();
3674
+ ```
3675
+
3622
3676
  ### Media types by file extension
3623
3677
 
3624
3678
  Source: `packages/reports/src/media-types.ts`. Import: `@crvouga/atlas/reports/media-types`
@@ -0,0 +1,169 @@
1
+ # Reference: runners
2
+
3
+ Native reporters that upload to the project server. Import paths `@crvouga/atlas/codecept`, `@crvouga/atlas/mocha`, `@crvouga/atlas/jest`, `@crvouga/atlas/node-test`.
4
+
5
+ > Generated from the doc comments of `packages/runners/src` by `bun run docs`. Do not edit: change the comment next to the code.
6
+
7
+ ### codecept.ts
8
+
9
+ Source: `packages/runners/src/codecept.ts`. Import: `@crvouga/atlas/codecept`
10
+
11
+ @module CodeceptJS reporter
12
+ CodeceptJS's Mocha reporter interface carries scenario outcomes and steps to the singleton Atlas server.
13
+
14
+ #### `AtlasCodeceptReporter`
15
+
16
+ ```ts
17
+ export default class AtlasCodeceptReporter extends AtlasMochaReporter
18
+ ```
19
+
20
+ Use `codeceptjs run --reporter
21
+
22
+ **Example**
23
+
24
+ ```ts
25
+ // codecept.conf.js
26
+ export const config = { tests: './tests/*_test.js', reporter: '@crvouga/atlas/codecept' };
27
+ ```
28
+
29
+ ### jest.ts
30
+
31
+ Source: `packages/runners/src/jest.ts`. Import: `@crvouga/atlas/jest`
32
+
33
+ @module Jest reporter
34
+ Uploads the aggregated results after Jest has finished, with suite crashes represented as failed checks.
35
+
36
+ #### `AtlasJestReporter`
37
+
38
+ ```ts
39
+ export default class AtlasJestReporter
40
+ ```
41
+
42
+ Jest's native reporter; retain `default` in the reporter list for console output. Upload errors fail Jest.
43
+
44
+ **Example**
45
+
46
+ ```ts
47
+ export default { reporters: ['default', ['@crvouga/atlas/jest', { url: 'https://atlas.example.com' }]] };
48
+ ```
49
+
50
+ - `onRunStart(result?: { numTotalTests?: number }): void` — Reset publishing errors for each watch-mode invocation.
51
+ - `onTestCaseStart(test: { path: string }, info: { fullName: string; title: string }): void` — Publish the test as running before its body executes in a worker.
52
+ - `onTestCaseResult(test: { path: string }, result: Result['testResults'][number]): void` — Stream each test outcome immediately, without waiting for other files to finish.
53
+ - `async onRunComplete(_contexts: unknown, result: { testResults: Result[] }): Promise<void>` — Await uploading every final result, including test-file failures before any test ran.
54
+ - `getLastError(): Error | undefined` — Jest uses this to turn a publishing error into a failed invocation.
55
+
56
+ ### mocha.ts
57
+
58
+ Source: `packages/runners/src/mocha.ts`. Import: `@crvouga/atlas/mocha`
59
+
60
+ @module Mocha and CodeceptJS reporter
61
+ Receives Mocha's final test outcomes, including CodeceptJS scenarios, and waits for the server upload in `done`.
62
+ No runner package is imported, so consumers use their own installed version.
63
+
64
+ #### `AtlasMochaReporter`
65
+
66
+ ```ts
67
+ export default class AtlasMochaReporter
68
+ ```
69
+
70
+ Mocha reporter; use `--reporter
71
+
72
+ **Example**
73
+
74
+ ```ts
75
+ // mocha configuration
76
+ export default { reporter: '@crvouga/atlas/mocha', reporterOptions: { url: 'https://atlas.example.com' } };
77
+ ```
78
+
79
+ - `done(failures: number, callback: (failures: number) => void): void` — Mocha waits for this callback before exiting. Publishing errors fail the invocation.
80
+
81
+ ### node-test.ts
82
+
83
+ Source: `packages/runners/src/node-test.ts`. Import: `@crvouga/atlas/node-test`
84
+
85
+ @module Node test runner reporter
86
+ Consumes Node's test event stream and publishes final leaf outcomes to the project Atlas server.
87
+
88
+ #### `atlasNodeReporter`
89
+
90
+ ```ts
91
+ export default async function* atlasNodeReporter(source: AsyncIterable<Event>): AsyncGenerator<string>
92
+ ```
93
+
94
+ Async generator reporter. Use alongside Node's spec reporter to keep normal output.
95
+ Upload errors reject the stream; skips and TODOs never count as passed. Successful suite containers are excluded; hook failures are retained.
96
+
97
+ **Example**
98
+
99
+ ```ts
100
+ // node --test --test-reporter=spec --test-reporter=@crvouga/atlas/node-test
101
+ ```
102
+
103
+ ### publish.ts
104
+
105
+ Source: `packages/runners/src/publish.ts`
106
+
107
+ @module Publishing native runner results
108
+ Native reporters send checks to the project's Atlas server. Only the server knows its storage backend.
109
+
110
+ #### `ReporterOptions`
111
+
112
+ ```ts
113
+ export type ReporterOptions = {
114
+ /** Project namespace; defaults to ATLAS_PROJECT. */
115
+ readonly project?: string;
116
+ readonly url?: string;
117
+ readonly token?: string;
118
+ readonly producer?: string;
119
+ readonly environment?: string;
120
+ readonly branch?: string;
121
+ readonly sha?: string;
122
+ readonly timeoutMs?: number;
123
+ readonly rootDir?: string;
124
+ /** Explicit final evidence time; normally generated when publication finishes. */
125
+ readonly recordedAtMs?: number;
126
+ };
127
+ ```
128
+
129
+ Reporter options; explicit values override ATLAS_SERVER_URL, ATLAS_PUBLISH_TOKEN, ATLAS_ENVIRONMENT, ATLAS_BRANCH and ATLAS_SHA.
130
+
131
+ #### `testFile`
132
+
133
+ ```ts
134
+ export function testFile(file: string | undefined, root = process.cwd()): string
135
+ ```
136
+
137
+ A portable check identity: paths relative to the project, using forward slashes on every OS.
138
+
139
+ #### `uploadChecks`
140
+
141
+ ```ts
142
+ export async function uploadChecks(runner: string, checks: readonly EvidenceCheck[], options: ReporterOptions = {}): Promise<void>
143
+ ```
144
+
145
+ Upload one completed runner invocation. Unknown outcomes stay not-run, repeated identities are numbered.
146
+ Throws on missing configuration, timeouts or refused uploads so CI cannot silently lose its evidence.
147
+ No storage credentials or backend URL are needed by the runner.
148
+
149
+ **Example**
150
+
151
+ ```ts
152
+ await uploadChecks('mocha', checks, { url: 'https://atlas.example.com', token: process.env.ATLAS_PUBLISH_TOKEN });
153
+ ```
154
+
155
+ #### `runnerLive`
156
+
157
+ ```ts
158
+ export function runnerLive(runner: string, options: ReporterOptions = {}, plannedChecks?: number): import('../../reports/src/live-reporter').LiveReporter
159
+ ```
160
+
161
+ Start the native runner's live feed with the same endpoint and identity as its final report.
162
+
163
+ #### `finishRunner`
164
+
165
+ ```ts
166
+ export async function finishRunner(live: import('../../reports/src/live-reporter').LiveReporter, runner: string, checks: readonly EvidenceCheck[], options: ReporterOptions = {}): Promise<void>
167
+ ```
168
+
169
+ Flush live progress, then publish the full final evidence even if the lease was interrupted. Reject either failure.
@@ -863,6 +863,62 @@ export type SpecSet = z.output<typeof SpecSetSchema>;
863
863
 
864
864
  A parsed `SpecSetSchema` value.
865
865
 
866
+ ### live-run.ts
867
+
868
+ Source: `packages/schema/src/live-run.ts`
869
+
870
+ @module Live run protocol
871
+ A live run is a producer lease, separate from immutable report layers. Server receipt times determine liveness.
872
+ Expiration means interrupted, never passed; completed check results retain the stable layer contract.
873
+
874
+ #### `LiveCheckSchema`
875
+
876
+ ```ts
877
+ export const LiveCheckSchema = …
878
+ ```
879
+
880
+ Small progress messages: no attachment bytes, bounded step history and text.
881
+
882
+ #### `LiveRunStartSchema`
883
+
884
+ ```ts
885
+ export const LiveRunStartSchema = …
886
+ ```
887
+
888
+ The producer starts one UUID-named run. Identity stays fixed for its lifetime.
889
+
890
+ #### `LiveRunUpdateSchema`
891
+
892
+ ```ts
893
+ export const LiveRunUpdateSchema = …
894
+ ```
895
+
896
+ Ordered deltas. A retried sequence must have identical content; terminal runs never accept more progress.
897
+
898
+ #### `LiveRunSchema`
899
+
900
+ ```ts
901
+ export const LiveRunSchema = …
902
+ ```
903
+
904
+ The bounded live feed response; completed and interrupted runs remain visible as recent history.
905
+
906
+ #### `LiveCheck`
907
+
908
+ ```ts
909
+ export type LiveCheck = z.input<typeof LiveCheckSchema>;
910
+ ```
911
+
912
+ The shape a producer uses for a progress check.
913
+
914
+ #### `LiveRun`
915
+
916
+ ```ts
917
+ export type LiveRun = z.output<typeof LiveRunSchema>;
918
+ ```
919
+
920
+ A server's live run snapshot.
921
+
866
922
  ### Statechart run manifest schemas
867
923
 
868
924
  Source: `packages/schema/src/manifest.ts`
@@ -1205,6 +1261,45 @@ The top level of `runs/<run-id>/manifest.json`. Sections are `unknown` here so a
1205
1261
  validate each record on its own and keep the valid ones; the record schemas above are the
1206
1262
  contract for each entry.
1207
1263
 
1264
+ ### project.ts
1265
+
1266
+ Source: `packages/schema/src/project.ts`
1267
+
1268
+ @module Project identity
1269
+ Project slugs partition every report, spec, recording and live run on an Atlas node.
1270
+
1271
+ #### `ProjectSlugSchema`
1272
+
1273
+ ```ts
1274
+ export const ProjectSlugSchema = …
1275
+ ```
1276
+
1277
+ A portable project namespace: 1–64 lowercase letters, digits or hyphens, starting and ending with a letter or digit.
1278
+
1279
+ #### `ProjectSchema`
1280
+
1281
+ ```ts
1282
+ export const ProjectSchema = …
1283
+ ```
1284
+
1285
+ One project advertised by a node. The default project contains uploads from older clients.
1286
+
1287
+ #### `ProjectsSchema`
1288
+
1289
+ ```ts
1290
+ export const ProjectsSchema = …
1291
+ ```
1292
+
1293
+ Project discovery response.
1294
+
1295
+ #### `Project`
1296
+
1297
+ ```ts
1298
+ export type Project = z.output<typeof ProjectSchema>;
1299
+ ```
1300
+
1301
+ A project's public identity.
1302
+
1208
1303
  ### Report sources and change notices
1209
1304
 
1210
1305
  Source: `packages/schema/src/report-sources.ts`
@@ -1399,6 +1494,22 @@ export type ServerConfigInput = z.input<typeof ServerConfigSchema>;
1399
1494
 
1400
1495
  A server configuration as written, before defaults are applied.
1401
1496
 
1497
+ #### `ServerEditableSettingsSchema`
1498
+
1499
+ ```ts
1500
+ export const ServerEditableSettingsSchema = …
1501
+ ```
1502
+
1503
+ Storage and report settings editable by an admin. Authentication, listeners and the served site stay deployment-owned. Replaces the editable portion of the file; omitted fields use defaults after restart.
1504
+
1505
+ #### `ServerSettingsSnapshotSchema`
1506
+
1507
+ ```ts
1508
+ export const ServerSettingsSnapshotSchema = …
1509
+ ```
1510
+
1511
+ The settings API response: persisted editable configuration, active values, concurrency revision and restart/precedence guidance. Authentication token settings are excluded.
1512
+
1402
1513
  ### Statechart spec schemas
1403
1514
 
1404
1515
  Source: `packages/schema/src/spec.ts`
@@ -24,6 +24,8 @@ export type AtlasServerOptions = {
24
24
  readonly backends: readonly BackendInput[];
25
25
  /** The id of the backend uploads go to. */
26
26
  readonly write: string;
27
+ /** Operational lease storage. Defaults to the write backend; HTTP relays use their own local store. */
28
+ readonly liveStore?: ObjectStore;
27
29
  readonly tokens?: readonly ResolvedToken[];
28
30
  readonly anonymousRead?: boolean;
29
31
  /** The built viewer. Without it, only the API is served. */
@@ -33,9 +35,15 @@ export type AtlasServerOptions = {
33
35
  readonly refreshMs?: number;
34
36
  readonly limits?: { readonly maxObjectMiB?: number; readonly maxJsonMiB?: number; readonly maxStreams?: number };
35
37
  readonly heartbeatMs?: number;
38
+ /** Producer lease timeout; default 30 seconds. */
39
+ readonly runTimeoutMs?: number;
40
+ /** Maximum time without progress; default 15 minutes, 0 disables. */
41
+ readonly runStallMs?: number;
36
42
  readonly log?: (line: Record<string, unknown>) => void;
37
43
  readonly now?: () => number;
38
44
  readonly version?: string;
45
+ /** Admin configuration persistence, supplied by atlas serve. Changes apply on restart. */
46
+ readonly settings?: ServerSettings;
39
47
  /** Extra runtime configuration written into the page. */
40
48
  readonly page?: Record<string, unknown>;
41
49
  };
@@ -252,9 +260,9 @@ A server that anyone on the network can upload to must say who may.
252
260
 
253
261
  Source: `packages/server/src/dev.ts`
254
262
 
255
- Runs the same server as `atlas serve` on a port of its own, with the viewer's Vite dev server in front of it. Vite proxies
263
+ Runs the same server as `atlas serve` on a port of its own, with the client's Vite dev server in front of it. Vite proxies
256
264
  `/api` (server-sent events included) to the server and writes the same address book into the page, so the browser, the
257
- uploaders and the viewer all use one host, exactly as in production, and a change to the viewer shows at once.
265
+ uploaders and the client all use one host, exactly as in production, and a change to the client shows at once.
258
266
 
259
267
  #### `DEV_USAGE`
260
268
 
@@ -277,7 +285,7 @@ export async function devMain(argv: readonly string[], io: ServeIo): Promise<num
277
285
  Source: `packages/server/src/events.ts`
278
286
 
279
287
  What the server tells every connected viewer the moment it happens. A viewer that misses some (a dropped connection, a
280
- laptop that slept) loses nothing: events say "something changed", the state is always fetched, and a viewer reads again
288
+ laptop that slept) loses nothing: report events announce changes, run events carry bounded progress snapshots, and a viewer reads authoritative state again
281
289
  when it reconnects. `EventHub` encodes each event once for all viewers, sends a named `ping` every heartbeat so proxies
282
290
  keep the connection and a quiet server is told apart from a dead one, refuses past a maximum number of viewers (they poll
283
291
  instead), and lets go of a viewer that has stopped reading rather than hold its backlog.
@@ -287,10 +295,10 @@ See docs/server.md#streaming
287
295
  #### `EventName`
288
296
 
289
297
  ```ts
290
- export type EventName = 'hello' | 'report' | 'atlas:change';
298
+ export type EventName = 'hello' | 'report' | 'atlas:change' | 'run' | 'projects';
291
299
  ```
292
300
 
293
- The events the server publishes: `hello` (sent first to a new viewer), `report` (layers arrived) and `atlas:change` (specs or runs changed). `ping` is the heartbeat, sent by the hub itself.
301
+ The events the server publishes: `hello` (sent first to a new viewer), `report` (layers arrived) `atlas:change` (specs or runs changed), `run` (a bounded live lease snapshot), and `projects` (project discovery changed). `ping` is the heartbeat, sent by the hub itself.
294
302
 
295
303
  #### `EventHub`
296
304
 
@@ -360,6 +368,44 @@ as one checked configuration), `serveMain` (what `atlas serve` runs), `EventHub`
360
368
 
361
369
  See docs/server.md
362
370
 
371
+ ### live-runs.ts
372
+
373
+ Source: `packages/server/src/live-runs.ts`
374
+
375
+ @module Live run leases
376
+ Tracks producer heartbeats independently of progress, publishes completed checks incrementally, and persists
377
+ bounded run summaries. A server restart interrupts outstanding leases rather than inventing continued progress.
378
+
379
+ #### `LiveRunError`
380
+
381
+ ```ts
382
+ export class LiveRunError extends Error
383
+ ```
384
+
385
+ A refused run update: 403 another publisher, 404 unknown run, 409 stale/terminal run, or 429 capacity.
386
+
387
+ #### `LiveRuns`
388
+
389
+ ```ts
390
+ export class LiveRuns
391
+ ```
392
+
393
+ One server's live feed. Heartbeats never create result layers. Persisted summaries contain at most 20 recent
394
+ checks and 10 active checks; completed results live in ordinary immutable layers. Leases expire by server time.
395
+
396
+ **Example**
397
+
398
+ ```ts
399
+ const live = new LiveRuns({ store, timeoutMs: 30_000, stalledMs: 900_000, onChange: () => {}, onLayer: async () => {} });
400
+ ```
401
+
402
+ - `readonly ready: Promise<void>;` — Resolves after restoring summaries and interrupting leases left by a previous server process.
403
+ - `async list(): Promise<LiveRun[]>` — Read the most recent 50 summaries. Progress detail and counters are already bounded and cached.
404
+ - `async start(input: Start, owner: string): Promise<LiveRun>` — Register one producer lease. Retrying an identical start is idempotent.
405
+ - `async heartbeat(id: string, owner: string): Promise<void>` — Renew liveness by server receipt time, independently of whether a test made progress.
406
+ - `async update(id: string, input: Update, owner: string): Promise<LiveRun>` — Apply one ordered progress batch and publish its completed checks before acknowledging it.
407
+ - `async sweep(): Promise<void>` — Expire silent producers and optional progress stalls. Heartbeats cannot revive an expired run.
408
+
363
409
  ### llms.txt of a running server
364
410
 
365
411
  Source: `packages/server/src/llms.ts`
@@ -397,7 +443,7 @@ The guide for one server, Markdown. Stable in structure: an agent may read it by
397
443
  Source: `packages/server/src/main.ts`
398
444
 
399
445
  Starts the Atlas server from its configuration: opens every backend (creating the data folder), proves the upload
400
- backend can be written to before listening, finds or builds the viewer, listens at once (so a liveness probe finds the
446
+ backend can be written to before listening, finds or builds the client, listens at once (so a liveness probe finds the
401
447
  process while a large backend is still being read), and stops cleanly on SIGTERM or SIGINT. `serverOptionsOf` is shared
402
448
  with `atlas dev`, so the two run the same server.
403
449
 
@@ -494,8 +540,7 @@ One operation of the API, and the route the server registers for it.
494
540
  export const OPERATIONS: readonly Operation[]
495
541
  ```
496
542
 
497
- Every operation of the API. The order is the order of the document. The descriptions are the documentation an
498
- agent reads, so they say what to send, what comes back, and what to do when it does not work.
543
+ Every node operation and the same stable report protocol inside each project namespace.
499
544
 
500
545
  #### `NOT_OPERATIONS`
501
546
 
@@ -541,21 +586,116 @@ The response made into a problem document when it is an error (status 400 or mor
541
586
  `error`. Headers (`WWW-Authenticate`, `Retry-After`, `Allow`) and any other body fields are kept. Anything else,
542
587
  including every success, is returned as it is.
543
588
 
544
- ### Finding the viewer
589
+ ### projects.ts
590
+
591
+ Source: `packages/server/src/projects.ts`
592
+
593
+ @module Project routing
594
+ One node serves isolated project stores through the same authenticated API. Legacy unqualified requests address
595
+ the default project; named projects use scoped stores without rewriting uploaded report documents.
596
+
597
+ #### `projectServer`
598
+
599
+ ```ts
600
+ export function projectServer(options: AtlasServerOptions, create: (options: AtlasServerOptions) => AtlasServer): AtlasServer
601
+ ```
602
+
603
+ Wrap the single-project protocol in project selection. Auth, upload validation and composition stay identical.
604
+
605
+ ### settings-page.ts
606
+
607
+ Source: `packages/server/src/settings-page.ts`
608
+
609
+ @module Settings page
610
+ A standalone administrator form, styled with the Atlas client's shared design tokens.
611
+ Credentials remain in page memory; the settings API owns authorization and validation.
612
+
613
+ #### `SETTINGS_PAGE`
614
+
615
+ ```ts
616
+ export const SETTINGS_PAGE = …
617
+ ```
618
+
619
+ The settings form and its browser controller, also usable without a built client.
620
+
621
+ #### `renderSettingsPage`
622
+
623
+ ```ts
624
+ export function renderSettingsPage(): Promise<string>
625
+ ```
626
+
627
+ Render Settings using the exact design tokens shipped with the Atlas client. Loads and caches the stylesheet
628
+ on first request, never at import time. Minimal neutral tokens keep embedded API-only servers usable too.
629
+
630
+ **Example**
631
+
632
+ ```ts
633
+ const html = await renderSettingsPage();
634
+ ```
635
+
636
+ ### settings.ts
637
+
638
+ Source: `packages/server/src/settings.ts`
639
+
640
+ @module Persisted server settings
641
+ The admin UI edits storage and report configuration in atlas.server.json. Changes take effect on restart;
642
+ environment variables and flags keep their documented precedence. Authentication is managed outside this UI.
643
+
644
+ #### `SettingsSnapshot`
645
+
646
+ ```ts
647
+ export type SettingsSnapshot = { readonly revision: string; readonly settings: Record<string, unknown>; readonly active: Record<string, unknown>; readonly restartRequired: boolean; readonly precedence: string };
648
+ ```
649
+
650
+ The persisted editable settings, active settings and a revision for optimistic concurrency. Authentication token settings are excluded.
651
+
652
+ #### `ServerSettings`
653
+
654
+ ```ts
655
+ export type ServerSettings = { read(): Promise<SettingsSnapshot>; save(settings: unknown, revision: string): Promise<SettingsSnapshot> };
656
+ ```
657
+
658
+ Storage for the admin API; saving is serialized and refuses a stale revision.
659
+
660
+ #### `fileServerSettings`
661
+
662
+ ```ts
663
+ export function fileServerSettings(config: ResolvedServerConfig, argv: readonly string[], env: Readonly<Record<string, string | undefined>>, cwd: string): ServerSettings
664
+ ```
665
+
666
+ Manage the server's configuration file with atomic replacement, preserving authentication and unrelated fields.
667
+ Validates both the file alone and the effective configuration before saving. Never migrates or deletes report data.
668
+ Changes need a server restart; deployment environment variables and flags override the file.
669
+
670
+ **Example**
671
+
672
+ ```ts
673
+ const settings = fileServerSettings(config, argv, process.env, process.cwd());
674
+ ```
675
+
676
+ ### Finding the client
545
677
 
546
678
  Source: `packages/server/src/site.ts`
547
679
 
548
- Locates the built viewer a server serves: the folder named by `--site`, `./atlas-site`, or one built once into the user's
549
- cache from the viewer source shipped in the package (`atlas-visualizer build --no-data`). The build is the same for every
680
+ Locates the built client a server serves: the folder named by `--site`, `./atlas-site`, or one built once into the user's
681
+ cache from the client source shipped in the package (`atlas client build --no-data`). The build is the same for every
550
682
  project because the server writes its own address book into the page, so it is built at most once per installed version.
551
683
 
684
+ #### `clientMain`
685
+
686
+ ```ts
687
+ export async function clientMain(argv: readonly string[]): Promise<number>
688
+ ```
689
+
690
+ Run the Atlas client build tools. Returns the child's exit code; throws when the client is missing.
691
+
552
692
  #### `visualizerRoot`
553
693
 
554
694
  ```ts
555
695
  export function visualizerRoot(): string | null
556
696
  ```
557
697
 
558
- Where the viewer's source is, in this repository and in an installed package.
698
+ Where the client's source is, in this repository and in an installed package.
559
699
 
560
700
  #### `buildSite`
561
701
 
@@ -563,7 +703,7 @@ Where the viewer's source is, in this repository and in an installed package.
563
703
  export async function buildSite(out: string, log: (text: string) => void): Promise<boolean>
564
704
  ```
565
705
 
566
- Build the viewer once into a folder, for a server that was not given a prebuilt one.
706
+ Build the client once into a folder, for a server that was not given a prebuilt one.
567
707
 
568
708
  #### `findSite`
569
709
 
@@ -571,7 +711,7 @@ Build the viewer once into a folder, for a server that was not given a prebuilt
571
711
  export async function findSite(named: string | undefined, cwd: string, log: (text: string) => void): Promise<string | null>
572
712
  ```
573
713
 
574
- The viewer to serve: the one named, a build beside the project, or one built into the user's cache.
714
+ The client to serve: the one named, a build beside the project, or one built into the user's cache.
575
715
 
576
716
  ### Store protocol routes
577
717