# Operations: configuration, credentials, data paths, backup and reset

Everything here is the behaviour of code in this repository on macOS with Apple Silicon. Commands
are copied from `package.json` and `tools/run-tool.mjs`; nothing is documented that does not exist.

## Prerequisites (measured on this machine)

| Need | Value | Why |
|---|---|---|
| Node | v24.21.0 (`~/.local/node-v24.21.0-darwin-arm64/bin/node`) | The API runs TypeScript directly (strip-only type stripping), so Node must be >= 24.19 and < 25 (`engines` in `package.json`) |
| pnpm | 11.25.0 | Workspace runner; `packageManager` pins it |
| SurrealDB | `surreal` 3.2.3+20260721.40522d1 on `PATH` (`/opt/homebrew/bin/surreal`) | Started by ATLAS itself; the store URI scheme is `surrealkv://` because `file://` no longer exists in 3.x |
| Google Chrome | any recent build at `/Applications/Google Chrome.app` | Only for `pnpm test:e2e`; the app itself runs in whatever browser you open |
| oMLX | optional, `http://127.0.0.1:8000/v1`, started by hand | Copilot only. ATLAS never starts, stops or reconfigures it |

`node tools/run-tool.mjs preflight` checks ports, the database, oMLX and fixtures, starts what is
missing, and prints a JSON report; it exits non-zero when something required is absent.

## Configuration

`.env.local` (gitignored) is the only configuration file. Copy `.env.example` to it, then fill
`SURREAL_ADMIN_PASSWORD`. `tools/services.mjs:loadEnv()` refuses to run without it and prints the
path it wanted. Real environment variables win over the file — the loader only sets a key when
`process.env[key] === undefined`, which is the same rule `apps/api/src/config.ts` applies, so
`ATLAS_AI_MAX_CONCURRENCY=4 pnpm dev` is a supported one-off override.

| Key | Default in code | Effect |
|---|---|---|
| `ATLAS_HOST` / `ATLAS_API_PORT` / `ATLAS_UI_PORT` | `127.0.0.1` / 8787 / 5173 | Loopback binding; the POC never binds `0.0.0.0` |
| `SURREAL_RPC_URL` / `SURREAL_HTTP_URL` | `ws://127.0.0.1:18080/rpc` / `http://127.0.0.1:18080` | The port is derived from the HTTP URL and is the one ATLAS starts or adopts |
| `SURREAL_NAMESPACE` / `SURREAL_DATABASE` | `spectra` / `atlas` | Scope of every query; also the namespace embedded in backups |
| `SURREAL_USERNAME` / `SURREAL_PASSWORD` | `atlas_app` | Application identity used by the API for reads and writes |
| `SURREAL_ADMIN_USERNAME` / `SURREAL_ADMIN_PASSWORD` | `root` | Migrations, backup, restore only |
| `OMLX_BASE_URL` / `OMLX_MODEL` / `OMLX_API_KEY` | `http://127.0.0.1:8000/v1`, model empty | Empty `OMLX_MODEL` means "discover from `/v1/models` at runtime" |
| `OMLX_EMBEDDING_MODEL` | empty | When unset or unverified, retrieval is lexical + graph and never vector search |
| `ATLAS_AI_MAX_CONCURRENCY` | 1 | One inference in flight, because the renderer and oMLX share this Mac |
| `ATLAS_SEED` | 20261016 | Deterministic synthetic data; same seed, same story numbers |
| `ATLAS_SCENARIO_ORIGIN` / `ATLAS_TIMEZONE` | 2026-10-16T08:00:00Z / Asia/Singapore | Stored in UTC, displayed in Singapore time |
| `ATLAS_SESSION_SECRET` | empty | HMAC key for the session cookie; see Credentials below |
| `ATLAS_MAX_UPLOAD_BYTES` | 2097152 | Ingestion fixture body limit, kept below the SurrealDB ~1 MB HTTP ceiling that matters for outbound bodies |
| `ATLAS_DATA_DIR` | `data` | Root for the store, evidence artifacts, backups, fixtures and logs |

## Credentials

- Two database identities only: `root` for DDL/backup/restore, and the non-privileged `atlas_app`
  that the API uses for application queries. Both live in `.env.local`, which is gitignored;
  `.env.example` keeps the fields empty.
- Nothing prints a credential. `/health` reports service names, latency and version strings; the UI's
  connectivity panel says so in its own header ("Addresses and readiness only — no credentials are
  read or shown here"), which journey 19 screenshots offline.
- The browser session is a signed, HttpOnly cookie (`atlas_session`) holding `{actor, role,
  workspaceId, segment}`, HMAC-SHA256-signed in `apps/api/src/session.ts`. It is deliberately not
  pretend enterprise SSO: it proves workspace scoping and role gating at the API boundary.
- `ATLAS_SESSION_SECRET` is read, never generated by this build. When it is empty the module falls
  back to `randomBytes(32)` per process, so every existing cookie stops validating after an API
  restart. Set a fixed random value if you want sessions to survive restarts; the value currently in
  `.env.local` was generated once by hand.

## Data paths

All paths are relative to the project root unless `ATLAS_DATA_DIR` is absolute. Everything below is
gitignored except `db/migrations/`.

| Path | Contents | Created by |
|---|---|---|
| `data/db/atlas-skv/` | SurrealKV store: the authoritative records | `ensureDatabase()` on first start; holds an exclusive lock |
| `data/evidence/defense/*.txt`, `data/evidence/commercial/*.txt` | Evidence artifacts quoted by `evidence` rows | seed (`pnpm demo`, `run-tool.mjs seed`) |
| `data/seeds/inventory-arrivals.{csv,json}` | Ingestion fixtures offered by the Data console, including deliberately invalid rows | seed (`writeIngestionFixtures()`) |
| `data/backups/atlas-<ISO timestamp>.json` | Logical backups (`atlas-backup-v1`) | `pnpm backup` |
| `data/logs/{surreal,api,web}.log` | stdout/stderr of the three detached processes | `startDetached()` in `tools/services.mjs` |
| `.runner/{surreal,api,web}.pid` | PID files used by `stopService()` and status | same |

## Start, stop, resume

```bash
cd "/Users/aquek/Documents/ChatGPT/SPECTRA Team/spectra-3d-atlas"
pnpm dev            # database -> API -> Vite dev server; prints what is listening and on which pid
pnpm start          # same, but the API serves apps/web/dist (build first with pnpm build)
pnpm stop           # SIGTERM the API and the UI; the database keeps running
node tools/run-tool.mjs stop --confirm   # also stop the database
```

`pnpm dev` is idempotent: each `ensure*()` adopts a service it already started (matching the store
path and port through `lsof`, because a process from an earlier session has no PID file) and leaves
it alone. If a port is held by something ATLAS did not start, it fails loudly instead of killing a
stranger:

```
Port 18080 is held by a process ATLAS did not start (...); free it or change SURREAL_HTTP_URL
```

That guard matters here: ports 8001/8002 run unrelated SurrealDB instances and 8000 is oMLX.

The API runs detached without a file watcher (`node apps/api/src/main.ts`). After editing anything
under `apps/api/` you must restart it — `pnpm stop` then `pnpm dev`, or kill the pid and re-run
`pnpm dev`. The UI has hot reload; the API does not.

## Backup and restore

```bash
pnpm backup                                  # -> data/backups/atlas-<timestamp>.json
node tools/run-tool.mjs backup --file probe-backup.json   # explicit path (project-relative or absolute)
pnpm restore --file data/backups/atlas-....json --confirm
```

A backup is a logical dump, not a file copy of the store, so it is reviewable and portable. Format
`atlas-backup-v1` carries: `format`, `createdAt`, server version, namespace/database, the top-level
ontology version, one entry per workspace (`uid`, `segment`, `ontologyVersion`, `lastSeq`), row
counts, eight captured traversal probes, an evidence manifest (`path`, `bytes`, `sha256`), and every
row of the 20 tables in restore order — relations reference entities and evidence references sources,
so ordering is load-bearing.

Rows are replayed as `UPSERT type::record(table, uid)` against the stored record id, which keeps both
uid-keyed entities and generated-id relation rows faithful. `entity_link` is the exception: a table
created by `RELATE` rejects a plain UPSERT, so its rows go back through the graph pattern with
literal `` table:`key` `` endpoints, because this build also cannot parse `type::record(...)` in
RELATE position. Object keys are quoted on the way out because reservation and transfer keys carry
dashes, and an unquoted object literal parses to nothing without complaining.

Restore verifies rather than assumes, and returns `ok: false` with a `failures[]` list when anything
disagrees: per-table row counts, each workspace's `ontologyVersion`, every probe re-walked through
`nativeNeighborhood()` at depth 2, and each evidence artifact's sha256. While it works the
out-of-band watcher is paused (`setReconciliationPaused(true)`) and each workspace gets a
`baseline.captured` journal event, so live clients re-read instead of trusting a dead cursor.

## Reset scope

```bash
pnpm reset:demo        # == node tools/run-tool.mjs reset --confirm
```

`reset` stops the UI and API, then the database (it holds the store lock), deletes
`data/db/atlas-skv/` only, restarts the database, re-runs `db/migrations/*.surql`, verifies the
`atlas_app` credential, re-seeds both workspaces and rewrites the ingestion fixtures. Evidence
artifacts on disk are left in place; a seeded store references them again by the same paths and
hashes. It is destructive to demonstration data and nothing else — no `rm -rf`, no other port, and
never oMLX.

`pnpm demo` is the gentler path: it seeds without deleting the store, and prints both story baselines
plus a ready URL. Seeding into an already-seeded store is safe: verification reconciles by uid set, so
it fails only on `missing` (records this seed failed to write) and reports everything else as `drift`
(`extra`, `extraUids`) instead of calling added rows a failure.

```bash
node tools/run-tool.mjs seed --clean   # prune synthetic strays + their edges, keep everything else
```

`--clean` deletes only entities the generator wrote that are no longer in its seed set
(`synthetic = true`), plus the relationships whose endpoint is one of them, and journals both
deletions so open tabs reconcile. Accepted ingestion rows are written `synthetic = false`, so
imported objects, their evidence, Ontology Studio types and scenario branches all survive and are
counted in `preserved`. Measured on this machine: one clean re-seed pruned 1 stray object and
preserved all 8 non-seed rows. `reset --confirm` is still the path to exact deterministic counts.

## oMLX policy

ATLAS reads `OMLX_BASE_URL` and nothing else about the model server. `pnpm probe:ai` lists
`/v1/models`, picks a chat model, and smoke-tests tool calling and streaming with measured latency.
When the endpoint is down the copilot panel says so and the retrieval answer path stays available;
no other feature depends on it. `ATLAS_AI_MAX_CONCURRENCY=1` keeps one inference in flight so the
renderer and the model do not fight over the same machine.

## Troubleshooting that is real here

- **`Missing .../.env.local`** — copy `.env.example` and fill `SURREAL_ADMIN_PASSWORD`.
- **Port held by a foreign process** — read the error; it names the pid and command. Do not kill
  8001/8002 (unrelated databases) or 8000 (oMLX).
- **HTTP 413 `length limit exceeded`** from the database — SurrealDB's HTTP bodies cap near 1 MB.
  History baselines are chunked into `snapshot_part` at 200,000 characters and restore batches are
  bounded by bytes (512 KiB) *and* statement count; keep new bulk paths chunked too.
- **`RELATE type::record(...)` parse error** — bind endpoints with `LET $in_x/$out_x` (see
  `docs/BUILD_STATUS.md`).
- **API changes not visible** — the API is detached and unwatched; restart it.
- **Session cookie rejected after an API restart** — `ATLAS_SESSION_SECRET` is empty, so the signing
  key was per-process. Set it in `.env.local`.
- **A long job dies when its shell exits** — start long work in the foreground of a live terminal,
  or use `pnpm dev`, which detaches and logs to `data/logs/`.
