@crvouga/atlas 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog: @crvouga/atlas
2
2
 
3
+ ## 0.9.0 (2026-10-15)
4
+
5
+ ### Features
6
+
7
+ - accept transition metadata ([6668573](https://github.com/crvouga/atlas/commit/666857395a702b7fc3319d7a31e90caaa060e8e9))
8
+
9
+ ## 0.8.0 (2026-10-15)
10
+
11
+ ### Features
12
+
13
+ - add anonymous Atlas server image (#12) ([269d62d](https://github.com/crvouga/atlas/commit/269d62d74e08efd2ba473dd3629bddfd13670265))
14
+
3
15
  ## 0.7.0 (2026-10-15)
4
16
 
5
17
  ### Features
package/README.md CHANGED
@@ -68,6 +68,48 @@ live step logs and a Running status filter. Producers send heartbeats every
68
68
  configure `ATLAS_RUN_TIMEOUT_SECONDS` and `ATLAS_RUN_STALL_SECONDS` (0 disables the progress timeout),
69
69
  or edit these settings in the client. An interruption never turns missing outcomes into passes.
70
70
 
71
+ ## Deploy to Railway without code
72
+
73
+ Copy this image URI into **Railway → New service → Docker image**:
74
+
75
+ ```text
76
+ ghcr.io/crvouga/atlas:latest
77
+ ```
78
+
79
+ The image includes the Atlas server and viewer; hosting it needs no repository, Dockerfile, build command
80
+ or package installation. [`latest` on GHCR](https://github.com/users/crvouga/packages/container/atlas) follows
81
+ the newest checked `main` image, independently of npm releases.
82
+
83
+ 1. Leave the start command unset. Add a long random secret as `ATLAS_TOKEN` (at least 12 characters;
84
+ use 48 or more) and set `RAILWAY_RUN_UID=0` for [Railway volume permissions](https://docs.railway.com/volumes#permissions).
85
+ 2. Attach a persistent volume at `/data` and keep one replica so reports and server settings survive updates.
86
+ 3. Set the healthcheck path to `/readyz` and generate a public domain. Railway supplies `PORT`;
87
+ leave `ATLAS_PORT` unset. Open the domain and sign in with the token as the password (any username).
88
+ 4. Enable **Settings → Source → Configure Auto Updates → Anytime** to follow `latest`.
89
+ [Railway checks for image updates periodically](https://docs.railway.com/deployments/image-auto-updates);
90
+ detection can take a few hours.
91
+
92
+ For the exact image from a run, open **[CI](https://github.com/crvouga/atlas/actions/workflows/ci.yml) →
93
+ run → Summary → Atlas CI**. The summary includes a copyable full commit tag, the immutable registry digest,
94
+ whether that run updated `latest`, all job results, npm release details and artifact links. Use the commit
95
+ tag to pin or roll back a deployment. The [deployment guide](docs/deploying.md#run-it) covers optional reader
96
+ and publisher tokens, bucket storage and sending results from your projects.
97
+
98
+ ### Deploy without authentication
99
+
100
+ For anonymous access, use this image in Railway instead:
101
+
102
+ ```text
103
+ ghcr.io/crvouga/atlas-insecure:latest
104
+ ```
105
+
106
+ It contains the same server and viewer, with anonymous reading, uploads and administration enabled.
107
+ Follow the Railway steps above, leaving `ATLAS_TOKEN`, `ATLAS_PUBLISH_TOKEN`, `ATLAS_READ_TOKEN` and
108
+ configuration-file auth tokens unset. Open the generated domain directly; no sign-in is required.
109
+ Anyone who discovers the URL can read reports, upload results and change settings. An unlisted URL
110
+ is not access control. See the [anonymous deployment guide](docs/deploying.md#deploy-without-authentication)
111
+ for a token-free project configuration and verification commands.
112
+
71
113
  ## For AI agents
72
114
 
73
115
  Atlas is built to be integrated and operated by agents, with nothing to click and nothing to guess.
@@ -187,9 +229,8 @@ unless you choose a bucket. `atlas dev` runs the same server with the client's d
187
229
  development is one host too. See [the server](docs/server.md) and
188
230
  [running Atlas for a project](docs/deploying.md).
189
231
 
190
- On Railway, deploy the image `ghcr.io/crvouga/atlas:latest` directly, with no project checkout or
191
- custom Dockerfile. Every green merge to `main` publishes it; enable Railway's image auto updates to follow it.
192
- The [deployment guide](docs/deploying.md#run-it) covers the token, persistent volume and healthcheck.
232
+ Deploy the server using the [Railway setup above](#deploy-to-railway-without-code), or follow the
233
+ [deployment guide](docs/deploying.md#run-it) for other hosts.
193
234
 
194
235
  ```sh
195
236
  atlas serve # the project's server, at :8080 (ATLAS_TOKEN says who may upload)
@@ -2,6 +2,7 @@ import { ChartGraph } from './graph.js';
2
2
  import { composeCharts } from './spec/compose.js';
3
3
  import { invokesOf, walkStates } from './spec/types.js';
4
4
  const STATE_KEYS = new Set(['id', 'initial', 'type', 'meta', 'states', 'on', 'invoke', 'description']);
5
+ const TRANSITION_KEYS = new Set(['target', 'meta']);
5
6
  const INVOKE_KEYS = new Set(['src', 'id', 'onDone']);
6
7
  const REQUIRED_META = ['description'];
7
8
  const FORBIDDEN = ['entry', 'exit', 'actions', 'assign', 'after', 'always', 'context', 'guard', 'cond', 'delay', 'output', 'onError'];
@@ -26,7 +27,7 @@ function checkShape(node, name, requiredMeta, out) {
26
27
  continue;
27
28
  }
28
29
  for (const key of Object.keys(t)) {
29
- if (key !== 'target')
30
+ if (!TRANSITION_KEYS.has(key))
30
31
  out.push({ rule: 'pure', message: `${name} --${event}-->: "${key}" is not allowed (no guards, no actions)` });
31
32
  }
32
33
  }
@@ -47,7 +48,10 @@ function sanitized(bundle) {
47
48
  if (STATE_KEYS.has(key))
48
49
  out[key] = value;
49
50
  if (node.on) {
50
- out['on'] = Object.fromEntries(Object.entries(node.on).map(([event, t]) => [event, typeof t === 'string' ? t : { target: t.target }]));
51
+ out['on'] = Object.fromEntries(Object.entries(node.on).map(([event, t]) => [
52
+ event,
53
+ typeof t === 'string' ? t : { target: t.target, ...('meta' in t ? { meta: t.meta } : {}) }
54
+ ]));
51
55
  }
52
56
  if (node.states)
53
57
  out['states'] = Object.fromEntries(Object.entries(node.states).map(([k, v]) => [k, clean(v)]));
@@ -18,7 +18,10 @@
18
18
  */
19
19
  /** The object form of a transition. `target` is a state name, optionally prefixed with `#` (a reference by name across the composed product). */
20
20
  export type TransitionConfig = {
21
+ /** The destination state, optionally prefixed with `#`. */
21
22
  target: string;
23
+ /** Declarative provenance or business metadata; ignored while executing the chart. */
24
+ meta?: Record<string, unknown>;
22
25
  };
23
26
  /** An `invoke` that runs another chart inside a state. It is never executed: `composeCharts` inlines the child chart statically. */
24
27
  export type InvokeConfig = {
@@ -84,7 +87,7 @@ export type StateConfig = {
84
87
  type?: 'parallel' | 'final';
85
88
  meta?: StateMeta;
86
89
  states?: Record<string, StateConfig>;
87
- /** Event name (exact business wording) to its transition: a target string or `{ target }`. */
90
+ /** Event name (exact business wording) to its transition: a target string or `{ target, meta? }`. */
88
91
  on?: Record<string, TransitionConfig | string>;
89
92
  /** Child chart(s) that run inside this state. */
90
93
  invoke?: InvokeConfig | InvokeConfig[];
@@ -144,9 +144,10 @@ export declare const StateMetaSchema: z.ZodObject<{
144
144
  }, z.core.$strip>;
145
145
  /** A parsed `StateMetaSchema` value. */
146
146
  export type StateMeta = z.infer<typeof StateMetaSchema>;
147
- /** The target of an event transition: a state name (or `#id`) written as a string, or an object with `target`. */
147
+ /** The target of an event transition: a state name (or `#id`) written as a string, or an object with `target` and optional declarative `meta`. */
148
148
  export declare const TransitionTargetSchema: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
149
149
  target: z.ZodString;
150
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
150
151
  }, z.core.$strip>]>;
151
152
  /** Kinds of state in the composed map: `atomic`, `compound`, `parallel`, `final`. */
152
153
  export declare const STATE_TYPES: readonly ["atomic", "compound", "parallel", "final"];
@@ -73,10 +73,13 @@ export const StateMetaShape = {
73
73
  };
74
74
  /** `StateMetaShape` with every field optional: the `meta` of a state or of a whole chart. */
75
75
  export const StateMetaSchema = z.object(StateMetaShape).partial();
76
- /** The target of an event transition: a state name (or `#id`) written as a string, or an object with `target`. */
76
+ /** The target of an event transition: a state name (or `#id`) written as a string, or an object with `target` and optional declarative `meta`. */
77
77
  export const TransitionTargetSchema = z.union([
78
78
  z.string().min(1),
79
- z.object({ target: z.string().min(1) })
79
+ z.object({
80
+ target: z.string().min(1),
81
+ meta: z.record(z.string(), z.unknown()).optional()
82
+ })
80
83
  ]);
81
84
  /** Kinds of state in the composed map: `atomic`, `compound`, `parallel`, `final`. */
82
85
  export const STATE_TYPES = ['atomic', 'compound', 'parallel', 'final'];
package/docs/deploying.md CHANGED
@@ -214,7 +214,15 @@ use a named `--store project=s3://… --write project` to put uploads in a share
214
214
  ### Run it
215
215
 
216
216
  **Railway (deploy an image, no repository or code changes).** Create a service from the Docker image
217
- `ghcr.io/crvouga/atlas:latest`.
217
+ `ghcr.io/crvouga/atlas:latest`. Copy the URI below into Railway's image field:
218
+
219
+ ```text
220
+ ghcr.io/crvouga/atlas:latest
221
+ ```
222
+
223
+ For a specific revision, open a [CI run](https://github.com/crvouga/atlas/actions/workflows/ci.yml)'s
224
+ **Summary → Atlas CI**: it includes the published commit image URI, immutable digest and whether that run
225
+ updated `latest`. Pull request builds and failed publications are labelled accordingly.
218
226
 
219
227
  1. In Railway choose **New service → Docker image** and enter `ghcr.io/crvouga/atlas:latest`.
220
228
  Leave the start command unset: the image includes the server and the prebuilt viewer.
@@ -239,7 +247,56 @@ your project's test results still needs the integration described there.
239
247
  For bucket storage instead, set `ATLAS_STORE=project=s3://my-bucket/atlas/my-project`, `ATLAS_WRITE=project`,
240
248
  and the bucket credentials. Keep the `/data` volume for server settings and local state.
241
249
 
242
- **Docker.** The same published image works anywhere Docker runs. It already contains the client (the same
250
+ ### Deploy without authentication
251
+
252
+ Use `ghcr.io/crvouga/atlas-insecure:latest` for a server that requires no sign-in or API token. It is built
253
+ from the same commit, server and viewer as `ghcr.io/crvouga/atlas:latest`, with `ATLAS_INSECURE=true`
254
+ to allow listening on the network without tokens. Anonymous callers have full read, publish and admin access.
255
+ Anyone who discovers an unlisted URL can read reports, upload results and change settings; the URL is
256
+ not access control.
257
+
258
+ In Railway, create a **Docker image** service with:
259
+
260
+ ```text
261
+ ghcr.io/crvouga/atlas-insecure:latest
262
+ ```
263
+
264
+ Leave the start command unset. Attach a persistent volume at `/data`, set `RAILWAY_RUN_UID=0`, keep one
265
+ replica, set the healthcheck to `/readyz` and generate a public domain. Leave `ATLAS_PORT` unset so Railway's
266
+ `PORT` is used. Enable **Settings → Source → Configure Auto Updates → Anytime** to follow `latest`.
267
+ Do not set `ATLAS_TOKEN`, `ATLAS_PUBLISH_TOKEN` or `ATLAS_READ_TOKEN`, or add `auth.tokens` to
268
+ `/data/atlas.server.json`: configured tokens still enable authentication. When reusing a volume from an
269
+ authenticated deployment, remove its saved auth tokens first. Open the domain directly, including `/admin`
270
+ for settings (leave the token field blank and choose **Load settings**).
271
+
272
+ Verify anonymous reads and uploads without an Authorization header:
273
+
274
+ ```sh
275
+ curl --fail https://YOUR-DOMAIN/readyz
276
+ curl --fail https://YOUR-DOMAIN/api/server
277
+ curl --fail 'https://YOUR-DOMAIN/api/checks?project=my-project' \
278
+ -H 'content-type: application/json' \
279
+ -d '{"producer":"smoke","checks":[{"id":"deployed","title":"Deployed","status":"passed"}]}'
280
+ ```
281
+
282
+ Readiness returns HTTP 200, `/api/server` reports `"open":true` and `"role":"admin"`, and the upload returns
283
+ HTTP 201. Configure a project with `ATLAS_STORE=https://YOUR-DOMAIN` and `ATLAS_PROJECT=my-project`;
284
+ leave its `ATLAS_TOKEN` unset. Use the same publishing commands described in
285
+ [sending results](#3-send-results-from-ci-and-from-laptops), omitting credentials.
286
+
287
+ For local Docker:
288
+
289
+ ```sh
290
+ docker run --pull always -p 8080:8080 -v atlas-data:/data ghcr.io/crvouga/atlas-insecure:latest
291
+ ```
292
+
293
+ Both images publish full commit tags for pinning or rollback. A new GHCR package must be made Public
294
+ once before Railway can pull without registry credentials; see
295
+ [image publication](https://github.com/crvouga/atlas/blob/main/docs/releasing.md#server-image).
296
+
297
+ ### Docker
298
+
299
+ The same published image works anywhere Docker runs. It already contains the client (the same
243
300
  for every project: the server tells the page where its data is), and keeps results on the volume at `/data`:
244
301
 
245
302
  ```sh
@@ -2241,7 +2241,12 @@ See docs/statecharts.md
2241
2241
  #### `TransitionConfig`
2242
2242
 
2243
2243
  ```ts
2244
- export type TransitionConfig = { target: string };
2244
+ export type TransitionConfig = {
2245
+ /** The destination state, optionally prefixed with `#`. */
2246
+ target: string;
2247
+ /** Declarative provenance or business metadata; ignored while executing the chart. */
2248
+ meta?: Record<string, unknown>;
2249
+ };
2245
2250
  ```
2246
2251
 
2247
2252
  The object form of a transition. `target` is a state name, optionally prefixed with `#` (a reference by name across the composed product).
@@ -2308,7 +2313,7 @@ export type StateConfig = {
2308
2313
  type?: 'parallel' | 'final';
2309
2314
  meta?: StateMeta;
2310
2315
  states?: Record<string, StateConfig>;
2311
- /** Event name (exact business wording) to its transition: a target string or `{ target }`. */
2316
+ /** Event name (exact business wording) to its transition: a target string or `{ target, meta? }`. */
2312
2317
  on?: Record<string, TransitionConfig | string>;
2313
2318
  /** Child chart(s) that run inside this state. */
2314
2319
  invoke?: InvokeConfig | InvokeConfig[];
@@ -1613,7 +1613,7 @@ A parsed `StateMetaSchema` value.
1613
1613
  export const TransitionTargetSchema = …
1614
1614
  ```
1615
1615
 
1616
- The target of an event transition: a state name (or `#id`) written as a string, or an object with `target`.
1616
+ The target of an event transition: a state name (or `#id`) written as a string, or an object with `target` and optional declarative `meta`.
1617
1617
 
1618
1618
  #### `STATE_TYPES`
1619
1619
 
package/docs/server.md CHANGED
@@ -56,6 +56,8 @@ ATLAS_STORE=http://127.0.0.1:5180 bunx atlas report publish --producer unit --ju
56
56
  For Railway, deploy `ghcr.io/crvouga/atlas:latest` directly: the image contains the server and
57
57
  viewer, and every green `main` push updates it. See [running Atlas for a project](deploying.md#run-it) for
58
58
  Railway's volume, tokens, healthcheck and image auto updates, or for Docker, Kubernetes and CI.
59
+ For anonymous reading, uploads and administration, use `ghcr.io/crvouga/atlas-insecure:latest` with no tokens
60
+ configured; see [deploying without authentication](deploying.md#deploy-without-authentication).
59
61
 
60
62
  ## Configuration
61
63
 
@@ -119,7 +119,8 @@ Atlas walks charts without running any code, so it executes only the parts of a
119
119
  are pure structure:
120
120
 
121
121
  - states, nesting (`initial`), `parallel` regions and `final` states;
122
- - event transitions, written as a target (`Opens: Open` or `{ target: '#Open' }`);
122
+ - event transitions, written as a target (`Opens: Open` or `{ target: '#Open' }`), with
123
+ optional declarative metadata (`{ target: '#Open', meta: { source: ['specs/door.feature'] } }`);
123
124
  - `invoke` that names another chart (`src`, `id`, `onDone`), composed statically;
124
125
  - `meta`.
125
126
 
package/llms-full.txt CHANGED
@@ -1008,6 +1008,8 @@ ATLAS_STORE=http://127.0.0.1:5180 bunx atlas report publish --producer unit --ju
1008
1008
  For Railway, deploy `ghcr.io/crvouga/atlas:latest` directly: the image contains the server and
1009
1009
  viewer, and every green `main` push updates it. See [running Atlas for a project](deploying.md#run-it) for
1010
1010
  Railway's volume, tokens, healthcheck and image auto updates, or for Docker, Kubernetes and CI.
1011
+ For anonymous reading, uploads and administration, use `ghcr.io/crvouga/atlas-insecure:latest` with no tokens
1012
+ configured; see [deploying without authentication](deploying.md#deploy-without-authentication).
1011
1013
 
1012
1014
  ## Configuration
1013
1015
 
@@ -1586,7 +1588,15 @@ use a named `--store project=s3://… --write project` to put uploads in a share
1586
1588
  ### Run it
1587
1589
 
1588
1590
  **Railway (deploy an image, no repository or code changes).** Create a service from the Docker image
1589
- `ghcr.io/crvouga/atlas:latest`.
1591
+ `ghcr.io/crvouga/atlas:latest`. Copy the URI below into Railway's image field:
1592
+
1593
+ ```text
1594
+ ghcr.io/crvouga/atlas:latest
1595
+ ```
1596
+
1597
+ For a specific revision, open a [CI run](https://github.com/crvouga/atlas/actions/workflows/ci.yml)'s
1598
+ **Summary → Atlas CI**: it includes the published commit image URI, immutable digest and whether that run
1599
+ updated `latest`. Pull request builds and failed publications are labelled accordingly.
1590
1600
 
1591
1601
  1. In Railway choose **New service → Docker image** and enter `ghcr.io/crvouga/atlas:latest`.
1592
1602
  Leave the start command unset: the image includes the server and the prebuilt viewer.
@@ -1611,7 +1621,56 @@ your project's test results still needs the integration described there.
1611
1621
  For bucket storage instead, set `ATLAS_STORE=project=s3://my-bucket/atlas/my-project`, `ATLAS_WRITE=project`,
1612
1622
  and the bucket credentials. Keep the `/data` volume for server settings and local state.
1613
1623
 
1614
- **Docker.** The same published image works anywhere Docker runs. It already contains the client (the same
1624
+ ### Deploy without authentication
1625
+
1626
+ Use `ghcr.io/crvouga/atlas-insecure:latest` for a server that requires no sign-in or API token. It is built
1627
+ from the same commit, server and viewer as `ghcr.io/crvouga/atlas:latest`, with `ATLAS_INSECURE=true`
1628
+ to allow listening on the network without tokens. Anonymous callers have full read, publish and admin access.
1629
+ Anyone who discovers an unlisted URL can read reports, upload results and change settings; the URL is
1630
+ not access control.
1631
+
1632
+ In Railway, create a **Docker image** service with:
1633
+
1634
+ ```text
1635
+ ghcr.io/crvouga/atlas-insecure:latest
1636
+ ```
1637
+
1638
+ Leave the start command unset. Attach a persistent volume at `/data`, set `RAILWAY_RUN_UID=0`, keep one
1639
+ replica, set the healthcheck to `/readyz` and generate a public domain. Leave `ATLAS_PORT` unset so Railway's
1640
+ `PORT` is used. Enable **Settings → Source → Configure Auto Updates → Anytime** to follow `latest`.
1641
+ Do not set `ATLAS_TOKEN`, `ATLAS_PUBLISH_TOKEN` or `ATLAS_READ_TOKEN`, or add `auth.tokens` to
1642
+ `/data/atlas.server.json`: configured tokens still enable authentication. When reusing a volume from an
1643
+ authenticated deployment, remove its saved auth tokens first. Open the domain directly, including `/admin`
1644
+ for settings (leave the token field blank and choose **Load settings**).
1645
+
1646
+ Verify anonymous reads and uploads without an Authorization header:
1647
+
1648
+ ```sh
1649
+ curl --fail https://YOUR-DOMAIN/readyz
1650
+ curl --fail https://YOUR-DOMAIN/api/server
1651
+ curl --fail 'https://YOUR-DOMAIN/api/checks?project=my-project' \
1652
+ -H 'content-type: application/json' \
1653
+ -d '{"producer":"smoke","checks":[{"id":"deployed","title":"Deployed","status":"passed"}]}'
1654
+ ```
1655
+
1656
+ Readiness returns HTTP 200, `/api/server` reports `"open":true` and `"role":"admin"`, and the upload returns
1657
+ HTTP 201. Configure a project with `ATLAS_STORE=https://YOUR-DOMAIN` and `ATLAS_PROJECT=my-project`;
1658
+ leave its `ATLAS_TOKEN` unset. Use the same publishing commands described in
1659
+ [sending results](#3-send-results-from-ci-and-from-laptops), omitting credentials.
1660
+
1661
+ For local Docker:
1662
+
1663
+ ```sh
1664
+ docker run --pull always -p 8080:8080 -v atlas-data:/data ghcr.io/crvouga/atlas-insecure:latest
1665
+ ```
1666
+
1667
+ Both images publish full commit tags for pinning or rollback. A new GHCR package must be made Public
1668
+ once before Railway can pull without registry credentials; see
1669
+ [image publication](https://github.com/crvouga/atlas/blob/main/docs/releasing.md#server-image).
1670
+
1671
+ ### Docker
1672
+
1673
+ The same published image works anywhere Docker runs. It already contains the client (the same
1615
1674
  for every project: the server tells the page where its data is), and keeps results on the volume at `/data`:
1616
1675
 
1617
1676
  ```sh
@@ -2668,7 +2727,8 @@ Atlas walks charts without running any code, so it executes only the parts of a
2668
2727
  are pure structure:
2669
2728
 
2670
2729
  - states, nesting (`initial`), `parallel` regions and `final` states;
2671
- - event transitions, written as a target (`Opens: Open` or `{ target: '#Open' }`);
2730
+ - event transitions, written as a target (`Opens: Open` or `{ target: '#Open' }`), with
2731
+ optional declarative metadata (`{ target: '#Open', meta: { source: ['specs/door.feature'] } }`);
2672
2732
  - `invoke` that names another chart (`src`, `id`, `onDone`), composed statically;
2673
2733
  - `meta`.
2674
2734
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crvouga/atlas",
3
- "version": "0.7.0",
3
+ "version": "0.9.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",
@@ -225,6 +225,13 @@
225
225
  "target": {
226
226
  "type": "string",
227
227
  "minLength": 1
228
+ },
229
+ "meta": {
230
+ "type": "object",
231
+ "propertyNames": {
232
+ "type": "string"
233
+ },
234
+ "additionalProperties": {}
228
235
  }
229
236
  },
230
237
  "required": [