# Control Center

Web-based operational visibility for fleets, devices, readiness, and alerts. Phase E1 ships a REST
API v1 and embedded UI served by the native CLI.

**Related:** [enterprise-operations-roadmap.md](./enterprise-operations-roadmap.md) ·
[device-pool.md](./device-pool.md) · [device-provisioning.md](./device-provisioning.md) ·
[telemetry-store.md](./telemetry-store.md) · [configuration.md](./configuration.md) ·
[troubleshooting.md](./troubleshooting.md) (stale CLI, `Unknown argument: generate`)

**Roadmap:** Control Center Pillar 6 enhancement backlog is **Stable** (see
[feature-status.md](./feature-status.md)).

---

## Quick start

```bash
# Generate a key: spanda control-center api-key generate --export
export SPANDA_API_KEY="my-local-dev-key"

# Installed spanda — start API + UI (default http://127.0.0.1:8080)
spanda control-center serve

# Release build — cargo build -p spanda --release from repo root
./target/release/spanda control-center serve

# With project configuration (device pool from spanda.toml)
spanda control-center serve --config spanda.toml --bind 0.0.0.0:8080
./target/release/spanda control-center serve --config spanda.toml --bind 0.0.0.0:8080

# Native gRPC (tonic) on a separate port
spanda control-center serve --grpc-bind 127.0.0.1:50051
./target/release/spanda control-center serve --grpc-bind 127.0.0.1:50051

# Inspect or stop local instances (use the same binary you started with)
spanda control-center status
spanda control-center status --discover   # find local listeners when port is unknown
spanda control-center stop                # stop all local Control Center servers
spanda control-center stop --url http://127.0.0.1:8080
spanda control-center status --url http://127.0.0.1:8081 --json

./target/release/spanda control-center status
./target/release/spanda control-center status --discover
./target/release/spanda control-center stop
./target/release/spanda control-center stop --url http://127.0.0.1:8080
```

Copy-paste `--config` / `--program` examples: [Local dev — Run with `--config` and
`--program`](#run-with-config-and-program).

### Instance lifecycle

`control-center serve` runs until stopped. Each start binds a TCP port; interrupted smoke tests and
`cargo run … serve` wrappers can leave orphaned listeners on random ports.

| Task | Command |
|------|---------|
| List running servers | `spanda control-center status --discover` |
| Inspect default (`8080`) | `spanda control-center status` |
| Stop all local servers | `spanda control-center stop` |
| Stop one URL | `spanda control-center stop --url http://127.0.0.1:8080` |
| Force kill | `spanda control-center stop --force` |

CI smoke scripts source
[`scripts/lib/control_center_smoke_lib.sh`](../scripts/lib/control_center_smoke_lib.sh) to kill the
listener on `CC_SMOKE_BIND` on `EXIT`, `INT`, and `TERM` (not only the `cargo run` wrapper PID).

### Access the UI

| Method | How |
|--------|-----|
| **Embedded UI** | Open `http://127.0.0.1:8080/` — React Control Center (same shell as `@davalgi-spanda/web`; rebuild with `scripts/sync_control_center_embedded_ui.sh` after UI changes) |
| **Web package** | Use the **Control Center** view in `@davalgi-spanda/web`; set the API URL to your serve address. Paste a Bearer token in-panel (or set `VITE_SPANDA_API_KEY` at build time). Role-aware tabs and an **Administration** console match the embedded UI. |
| **Desktop (Tauri)** | Start the API first, then `npm run control-center:desktop:dev` (Vite on port **5174**); optional `VITE_CONTROL_CENTER_URL=http://host:port`. Sign in via the in-app token banner — no rebuild required for operator keys. |
| **Remote CLI** | `export SPANDA_CONTROL_CENTER_URL=http://127.0.0.1:8080` then `spanda control-center dashboard` (see [Remote CLI](#remote-cli-rest-parity)) |

Read-only views and `GET /v1/*` endpoints work without authentication. Mutations (`POST`, `PATCH`,
`DELETE`) require a Bearer token — configure keys on the server before calling those endpoints.

### Local dev: start & rebuild

Control Center has three layers. The API must run separately for every UI path except read-only CLI
inspection.

| Layer | Package / crate | Role |
|-------|-----------------|------|
| API backend | `spanda-api` via `spanda control-center serve` | REST `/v1/*`, gRPC, embedded static UI |
| React UI | `packages/web` (`@davalgi-spanda/web`) | Panels shared by browser, desktop, and web playground |
| Desktop shell | `packages/control-center-desktop` (Tauri + Vite) | Native window; does not embed the Rust API |

#### Start (typical dev)

**One-time** (from repo root):

```bash
npm install
```

**Terminal 1 — API** (pick one invocation):

```bash
export SPANDA_API_KEY="my-local-dev-key"   # optional; see Authentication below

# Installed spanda
spanda control-center stop
spanda control-center serve --bind 127.0.0.1:8080

# Release build from repo root (cargo build -p spanda --release)
./target/release/spanda control-center stop
./target/release/spanda control-center serve --bind 127.0.0.1:8080

# Active Rust development (rebuilds on each run)
cargo run -p spanda -- control-center stop
cargo run -p spanda -- control-center serve --bind 127.0.0.1:8080
```

With `--config` and `--program`, see [Run with `--config` and
`--program`](#run-with-config-and-program).

**Browser only:** open `http://127.0.0.1:8080/` — no second terminal.

**Terminal 2 — desktop** (optional; hot-reloads React changes):

```bash
npm run control-center:desktop:dev
```

Equivalent: `npm run dev --workspace=@spanda/control-center-desktop`. Vite listens on port **5174**.
Point at a non-default API with `VITE_CONTROL_CENTER_URL=http://host:port`.

More detail:
[packages/control-center-desktop/README.md](../packages/control-center-desktop/README.md).

#### Rebuild after code changes

| What you changed | What to rebuild |
|------------------|-----------------|
| Rust API (`crates/spanda-api`, CLI handlers, ops core) | Stop `serve`, then `cargo run -p spanda -- control-center serve …` (dev) or `cargo build -p spanda --release` (install a release binary). If `spanda` on `PATH` is stale, use `cargo run` or reinstall — see [troubleshooting.md — Stale CLI binary](./troubleshooting.md#stale-cli-binary). |
| React panels (`packages/web`) for **embedded browser UI** (`http://127.0.0.1:8080`) | `./scripts/sync_control_center_embedded_ui.sh` then restart `control-center serve`. The script runs `npm run build:control-center --workspace=@davalgi-spanda/web` and copies `dist-control-center/` into `crates/spanda-api/src/static/control-center-ui/`. |
| React panels for **desktop dev** | Usually nothing — `control-center:desktop:dev` hot-reloads. Restart the dev shell if Vite gets stuck. |
| Desktop shell (Tauri config, native plugins) | `npm run control-center:desktop:check` (compile check) or `npm run build --workspace=@spanda/control-center-desktop` (production bundle). Maintainer releases: [desktop-release-runbook.md](./desktop-release-runbook.md) (`TAURI_BUILD=1`). |
| Operator API keys only | No rebuild — paste the token in the in-app banner or set `SPANDA_API_KEY` and restart `serve`. |

**Embedded UI sync (browser):**

```bash
./scripts/sync_control_center_embedded_ui.sh

spanda control-center stop
spanda control-center serve --bind 127.0.0.1:8080

./target/release/spanda control-center stop
./target/release/spanda control-center serve --bind 127.0.0.1:8080
```

**Production desktop bundle:**

```bash
npm run build --workspace=@spanda/control-center-desktop
# Artifacts: packages/control-center-desktop/src-tauri/target/release/bundle/
```

Or `TAURI_BUILD=1 ./scripts/build_control_center_desktop.sh` for the CI-style installer path.

#### Run with `--config` and `--program`

`control-center serve` accepts two optional paths. They are **independent** — mix any config with
any program, or pass only one.

| Flag | Path | What it loads |
|------|------|----------------|
| **`--config`** | Project directory **or** `spanda.toml` file | Device pool, discovery fixtures, facilities, fleet layout, Smart Spaces / ADAS device trees, HRI policy seeds |
| **`--program`** | `.sd` mission file | Default program for readiness, simulation, replay, traceability, govern-and-trace, recovery context |

**Path forms:** `--config` accepts either a directory (looks for `spanda.toml` inside) or the file
directly — both work:

```bash
--config crates/spanda-config/tests/fixtures/warehouse
--config crates/spanda-config/tests/fixtures/warehouse/spanda.toml   # equivalent
```

Run from the **repository root** unless paths are absolute. After changing flags, stop and restart
`serve` (see [Which `spanda`?](#which-spanda) below).

**What needs which flag:**

| UI / API area | `--config` | `--program` |
|---------------|:----------:|:-----------:|
| Dashboard (pool summary, alerts) | helpful | optional |
| Devices, Discovery, Provisioning, Mapping, Health | **required** for real pool data | — |
| Readiness, Simulation, Replay, Digital Thread, Traceability | — | **required** (or pass `file` in API body) |
| Smart Spaces, Humans, ADAS vehicle panels | **required** (matching blueprint) | recommended |
| Recovery orchestrator | optional | recommended |
| Govern-and-trace, drift vs baseline | **required** | **required** |

##### Which `spanda`?

Examples use **both** common invocations. Paths (`--config`, `--program`) are identical — only the
binary prefix changes.

| Invocation | When to use | Stop a local server |
|------------|-------------|---------------------|
| **`./target/release/spanda`** | Repo checkout; run `cargo build -p spanda --release` first | `./target/release/spanda control-center stop` |
| **`spanda`** | Installed CLI on `PATH` (`cargo install`, package manager, CI image) | `spanda control-center stop` |
| **`cargo run -p spanda --`** | Active Rust development (rebuilds on each run) | `cargo run -p spanda -- control-center stop` |

```bash
export SPANDA_API_KEY="my-local-dev-key"   # optional; see Authentication below

# Release build (from repository root):
cargo build -p spanda --release

# Verify which binary you are using:
./target/release/spanda --version
spanda --version
```

If `spanda control-center` is missing or flags like `api-key generate` fail, the installed binary
may be stale — see [troubleshooting.md — Stale CLI binary](./troubleshooting.md#stale-cli-binary).
Use `./target/release/spanda` from a fresh `cargo build` or reinstall.

##### Copy-paste examples

Each example shows **release build** and **installed** forms. Swap `--config` / `--program` as
needed; add `--bind 127.0.0.1:8080` to override the default listen address.

**Enterprise ops** — warehouse device pool + compliance rover (same as
`scripts/enterprise_ops_smoke.sh`):

```bash
./target/release/spanda control-center serve \
  --config crates/spanda-config/tests/fixtures/warehouse/spanda.toml \
  --program examples/showcase/compliance/defense_rover.sd

spanda control-center serve \
  --config crates/spanda-config/tests/fixtures/warehouse/spanda.toml \
  --program examples/showcase/compliance/defense_rover.sd
```

**Flagship rover:**

```bash
./target/release/spanda control-center serve \
  --config examples/showcase/autonomous_rover/spanda.toml \
  --program examples/showcase/autonomous_rover/src/rover.sd

spanda control-center serve \
  --config examples/showcase/autonomous_rover/spanda.toml \
  --program examples/showcase/autonomous_rover/src/rover.sd
```

**Program only** (readiness / sim; no device pool):

```bash
./target/release/spanda control-center serve \
  --program examples/rover.sd

spanda control-center serve \
  --program examples/rover.sd
```

**ADAS** — vehicle device tree + highway scenario:

```bash
./target/release/spanda control-center serve \
  --config examples/solutions/adas/spanda.toml \
  --program examples/solutions/adas/src/highway_drive.sd

spanda control-center serve \
  --config examples/solutions/adas/spanda.toml \
  --program examples/solutions/adas/src/highway_drive.sd
```

**Smart Spaces** — building blueprint:

```bash
./target/release/spanda control-center serve \
  --config examples/solutions/smart-spaces/spanda.toml \
  --program examples/solutions/smart-spaces/smart-building/floor_readiness.sd

spanda control-center serve \
  --config examples/solutions/smart-spaces/spanda.toml \
  --program examples/solutions/smart-spaces/smart-building/floor_readiness.sd
```

**Humans / spatial computing:**

```bash
./target/release/spanda control-center serve \
  --config examples/solutions/spatial-computing/spanda.toml \
  --program examples/solutions/spatial-computing/warehouse-ar/pick_mission.sd

spanda control-center serve \
  --config examples/solutions/spatial-computing/spanda.toml \
  --program examples/solutions/spatial-computing/warehouse-ar/pick_mission.sd
```

**Recovery orchestrator:**

```bash
./target/release/spanda control-center serve \
  --program examples/showcase/self_healing/rover.sd

spanda control-center serve \
  --program examples/showcase/self_healing/rover.sd
```

**Warehouse logistics** (end-to-end package):

```bash
./target/release/spanda control-center serve \
  --config examples/end_to_end/warehouse_delivery/spanda.toml \
  --program examples/end_to_end/warehouse_delivery/src/main.sd

spanda control-center serve \
  --config examples/end_to_end/warehouse_delivery/spanda.toml \
  --program examples/end_to_end/warehouse_delivery/src/main.sd
```

**Basic packaged project:**

```bash
./target/release/spanda control-center serve \
  --config examples/packages/basic_project/spanda.toml \
  --program examples/packages/basic_project/src/main.sd

spanda control-center serve \
  --config examples/packages/basic_project/spanda.toml \
  --program examples/packages/basic_project/src/main.sd
```

**Optional bind address** (default `127.0.0.1:8080`):

```bash
./target/release/spanda control-center serve \
  --bind 127.0.0.1:8080 \
  --config crates/spanda-config/tests/fixtures/warehouse/spanda.toml \
  --program examples/showcase/compliance/defense_rover.sd

spanda control-center serve \
  --bind 127.0.0.1:8080 \
  --config crates/spanda-config/tests/fixtures/warehouse/spanda.toml \
  --program examples/showcase/compliance/defense_rover.sd
```

##### Mix `--config` and `--program`

Each subsection lists **release build** then **installed** commands with the same flags.

**Same config, different program** — swap `--program` only:

Release build:

```bash
./target/release/spanda control-center serve \
  --config examples/solutions/smart-spaces/spanda.toml \
  --program examples/solutions/smart-spaces/smart-home/night_mode.sd

./target/release/spanda control-center serve \
  --config examples/solutions/smart-spaces/spanda.toml \
  --program examples/solutions/smart-spaces/emergency-response/fire_response.sd

./target/release/spanda control-center serve \
  --config examples/solutions/adas/spanda.toml \
  --program examples/solutions/adas/lane_keeping/lane_keeping.sd

./target/release/spanda control-center serve \
  --config examples/solutions/adas/spanda.toml \
  --program examples/solutions/adas/automatic_emergency_braking/aeb.sd
```

Installed:

```bash
spanda control-center serve \
  --config examples/solutions/smart-spaces/spanda.toml \
  --program examples/solutions/smart-spaces/smart-home/night_mode.sd

spanda control-center serve \
  --config examples/solutions/smart-spaces/spanda.toml \
  --program examples/solutions/smart-spaces/emergency-response/fire_response.sd

spanda control-center serve \
  --config examples/solutions/adas/spanda.toml \
  --program examples/solutions/adas/lane_keeping/lane_keeping.sd

spanda control-center serve \
  --config examples/solutions/adas/spanda.toml \
  --program examples/solutions/adas/automatic_emergency_braking/aeb.sd
```

**Different config, same program** — devices / discovery tabs change:

Release build:

```bash
./target/release/spanda control-center serve \
  --config crates/spanda-config/tests/fixtures/warehouse/spanda.toml \
  --program examples/showcase/compliance/defense_rover.sd

./target/release/spanda control-center serve \
  --config examples/packages/basic_project/spanda.toml \
  --program examples/showcase/compliance/defense_rover.sd

./target/release/spanda control-center serve \
  --config examples/solutions/adas/applications/truck/spanda.toml \
  --program examples/solutions/adas/src/highway_drive.sd

./target/release/spanda control-center serve \
  --config examples/solutions/adas/applications/shuttle/spanda.toml \
  --program examples/solutions/adas/src/highway_drive.sd
```

Installed:

```bash
spanda control-center serve \
  --config crates/spanda-config/tests/fixtures/warehouse/spanda.toml \
  --program examples/showcase/compliance/defense_rover.sd

spanda control-center serve \
  --config examples/packages/basic_project/spanda.toml \
  --program examples/showcase/compliance/defense_rover.sd

spanda control-center serve \
  --config examples/solutions/adas/applications/truck/spanda.toml \
  --program examples/solutions/adas/src/highway_drive.sd

spanda control-center serve \
  --config examples/solutions/adas/applications/shuttle/spanda.toml \
  --program examples/solutions/adas/src/highway_drive.sd
```

See [solutions/adas/applications](../examples/solutions/adas/applications/README.md) for more ADAS
device trees.

**Config only** (pool panels; no default mission):

```bash
./target/release/spanda control-center serve \
  --config examples/solutions/smart-spaces/spanda.toml

spanda control-center serve \
  --config examples/solutions/smart-spaces/spanda.toml
```

##### CLI demo → serve

```bash
./target/release/spanda demo adas
./target/release/spanda control-center stop
./target/release/spanda control-center serve \
  --config examples/solutions/adas/spanda.toml \
  --program examples/solutions/adas/src/highway_drive.sd

spanda demo adas
spanda control-center stop
spanda control-center serve \
  --config examples/solutions/adas/spanda.toml \
  --program examples/solutions/adas/src/highway_drive.sd
```

Open `http://127.0.0.1:8080/` or run `npm run control-center:desktop:dev` in a second terminal. Demo
catalog: [overview/flagship-demos.md](./overview/flagship-demos.md) ·
[showcase/README.md](../examples/showcase/README.md).

| Demo / UI focus | Highlights |
|-----------------|------------|
| Enterprise ops smoke | Warehouse pool + compliance rover |
| Flagship rover | `spanda demo rover` |
| ADAS | [ADAS dashboard](#adas-dashboard) |
| Smart Spaces | [Smart Spaces dashboard](#smart-spaces-dashboard) |
| Humans / spatial | [Human Interaction dashboard](#human-interaction-dashboard) |
| Recovery | [Recovery dashboard](#recovery-dashboard) |
| Warehouse logistics | [solutions/warehouse.md](./solutions/warehouse.md) |

---

## Authentication & API keys

Generate a random operator token with the CLI, or choose your own secret for local dev. Clients send
the token as `Authorization: Bearer <token>` on mutating requests.

### Generate a key (recommended)

```bash
# Human-readable output with setup steps
spanda control-center api-key generate

# Single copy-paste line for your shell
spanda control-center api-key generate --export
```

Example:

```bash
eval "$(spanda control-center api-key generate --export)"
spanda control-center serve
```

When `SPANDA_API_KEY` is unset and no `SPANDA_API_KEYS_FILE` is loaded, `control-center serve`
prints a warning with the same generate command.

If `generate` fails with `Unknown argument: generate` and top-level help has no `control-center`
section, reinstall the CLI — see [troubleshooting.md — Unknown argument:
generate](./troubleshooting.md#unknown-argument-generate-misleading-error).

The embedded Control Center UI shows an **Operator API key** banner when no token is configured in
the browser. Paste a token registered on the server (`SPANDA_API_KEYS_FILE` or `SPANDA_API_KEY` at
`serve` time). With **Remember on this browser** checked (default), the token is stored in
`localStorage` scoped to the Control Center origin (`host:port`) and restored on refresh; use
**Forget token** to clear it. The UI verifies the token with `POST /v1/alerts/test` before saving.
Same-origin XSS could read a stored token — use **Forget** on shared machines. The
`@davalgi-spanda/web` panel uses `VITE_SPANDA_API_KEY` at dev/build time instead.

**Browser storage key:** `spanda.control_center.bearer_token.v1:<host>` (for example
`spanda.control_center.bearer_token.v1:127.0.0.1:8080`). Each `host:port` has its own entry;
changing bind port or hostname requires pasting the token again unless you migrate the key in
DevTools → Application → Local Storage.

### Single operator key (local dev)

Any string works for quick local testing:

```bash
export SPANDA_API_KEY="my-local-dev-key"
spanda control-center serve
```

Use the **same value** from clients:

```bash
export SPANDA_API_KEY="my-local-dev-key"
curl -H "Authorization: Bearer $SPANDA_API_KEY" \
  -X POST http://127.0.0.1:8080/v1/alerts/test
```

A key from `SPANDA_API_KEY` is registered as role **administrator** (full mutation access).

### Alternative: openssl or Python

```bash
openssl rand -hex 32
# or
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
```

Export the output as `SPANDA_API_KEY` on the server and distribute it securely to operators. Do not
commit tokens to git.

### Multiple keys with roles

For several operators with different permissions, write a JSON file and point the server at it:

```bash
export SPANDA_API_KEYS_FILE=~/.spanda/api-keys.json
spanda control-center serve
```

Example `api-keys.json`:

```json
[
  {
    "key_id": "alice",
    "token": "alice-admin-token-here",
    "role": "administrator",
    "tenant_id": "default"
  },
  {
    "key_id": "bob",
    "token": "bob-operator-token-here",
    "role": "operator",
    "tenant_id": "default"
  }
]
```

Keys from `SPANDA_API_KEYS_FILE` are **merged** with any key from `SPANDA_API_KEY`.

| Role | Typical use |
|------|-------------|
| `administrator` | Full access (default for `SPANDA_API_KEY`) |
| `supervisor` | All actions except delete |
| `developer` | Deploy and operate |
| `operator` | Operate, shutdown, recover |
| `safety_officer` | Operate, approve, shutdown |
| `auditor` | Read-only (no mutations) |
| `guest` | Read-only (no mutations) |

Inspect the live permission matrix: `GET /v1/rbac/matrix`. After pasting a Bearer token in the UI
(embedded HTML or `@davalgi-spanda/web` / Tauri desktop), `GET /v1/rbac/me` returns your `role`,
`key_id`, `tenant_id`, and allowed mutation `permissions` — the UI uses this to show your role
badge, filter tabs by role, and enable action buttons per permission.

### Administration (administrator role)

The **Administration** tab (administrator role only) manages operator keys and integrations:

| Endpoint | Description |
|----------|-------------|
| `GET /v1/admin/api-keys` | List key metadata (no token values) |
| `POST /v1/admin/api-keys` | Create a key — response includes `token` once |
| `PATCH /v1/admin/api-keys/{key_id}` | Update role or label |
| `DELETE /v1/admin/api-keys/{key_id}` | Revoke a file-backed key (`env-default` from `SPANDA_API_KEY` cannot be revoked via API) |
| `GET /v1/admin/integrations` | Alert channels and observability backend summary |

File-backed keys persist to `SPANDA_API_KEYS_FILE` or `.spanda/api-keys.json`.

### User directory

| Endpoint | Description |
|----------|-------------|
| `GET /v1/admin/users` | List operator accounts (seeds from `[[humans]]` in config when empty) |
| `POST /v1/admin/users` | Create user |
| `PATCH /v1/admin/users/{user_id}` | Update display name, email, role, linked API key, enabled flag |
| `DELETE /v1/admin/users/{user_id}` | Remove user |

Persisted to `.spanda/admin-users.json`.

### Alert channels

| Endpoint | Description |
|----------|-------------|
| `GET /v1/admin/alert-channels` | Current dispatcher channels |
| `PUT /v1/admin/alert-channels` | Replace channels (webhook, email, PagerDuty, Teams, log) |

Persisted to `.spanda/alert-channels.json`. When unset, channels load from `SPANDA_ALERT_*`
environment variables.

### Simulation and Replay

| Endpoint | Description |
|----------|-------------|
| `GET /v1/programs/traces` | List `.trace` files under the project root |
| `POST /v1/programs/simulation` | Plan or execute simulation (`execute`, `inject_health_faults`, `record_trace`, `decision_trace`) |
| `POST /v1/programs/replay` | Inspect, deterministic replay, or playback |

Control Center **Simulation** and **Replay** tabs expose these workflows in the desktop and web
panel.

### Mission control

| Endpoint | Description |
|----------|-------------|
| `GET /v1/operator/missions` | Mission entities from the loaded program |
| `POST /v1/operator/mission/pause` | Pause mission (Operate) |
| `POST /v1/operator/mission/resume` | Resume mission (Operate) |
| `POST /v1/operator/mission/cancel` | Cancel mission (Shutdown) |
| `GET /v1/operator/mission/approvals` | Pending approval queue |
| `POST /v1/operator/mission/approve` | Approve or reject (Approve) |

### Differentiation (NOW signature capabilities)

| Endpoint | Description |
|----------|-------------|
| `GET /v1/programs/source` | Source text for the loaded `--program` file |
| `POST /v1/programs/contract/verify` | Mission contract verification (`spanda contract verify` parity) |
| `POST /v1/programs/explain` | Explainability report — body `{ "mode": "program\|readiness\|verify\|safety\|decision\|trace", "file": "…" }` |
| `POST /v1/programs/audit/decisions` | Decision audit trail from a `.trace` — optional `"explain": true` |

Control Center **Differentiation** tab (Governance group) surfaces contract verify, explain modes,
and trace audit. **Assurance/Diagnosis** runs `POST /v1/programs/assure` and `/diagnose`.
**Traceability** loads the capability matrix via `POST /v1/programs/verify/capabilities` with
`traceability: true`. **Playground** can load the server program via `GET /v1/programs/source`.

### What requires authentication

| Access | Auth required? |
|--------|----------------|
| Embedded UI at `/` | No (read-only panels) |
| `GET /v1/*` (dashboard, devices, health, OpenAPI, …) | No |
| `POST` / `PATCH` / `PUT` / `DELETE` (provision, OTA, approvals, quarantine, …) | Yes — `Authorization: Bearer <token>` |
| gRPC mutating RPCs | Same Bearer token in metadata |

**Multi-tenant isolation:** Set `SPANDA_TENANT_ID` on the Control Center instance (default
`default`). Each key may include a `tenant_id` field; authenticated requests with a mismatched
tenant return `403`.

**Storage:** Server-side keys are matched as plain strings (not hashed). Treat them like passwords —
restrict file permissions on `SPANDA_API_KEYS_FILE` and rotate on compromise. The embedded UI may
optionally persist a pasted Bearer token in browser `localStorage` under
`spanda.control_center.bearer_token.v1:<host>` (per `host:port`, opt-in via **Remember on this
browser**); this does not register the key on the server and is readable by any script on the same
origin. Clear with **Forget token** or remove the key in browser DevTools.

### ADAS dashboard

Launch with the ADAS Solution Blueprint for vehicle-centric operations:

```bash
./target/release/spanda control-center serve \
  --config examples/solutions/adas/spanda.toml \
  --program examples/solutions/adas/src/highway_drive.sd

spanda control-center serve \
  --config examples/solutions/adas/spanda.toml \
  --program examples/solutions/adas/src/highway_drive.sd
```

The **ADAS** tab shows vehicle health, sensor health, readiness pool summary, trust score, alerts,
OTA status, assurance/diagnosis summaries, and replay workflow links. See
[solutions/adas.md](./solutions/adas.md).

### Recovery dashboard

Launch with the self-healing showcase program:

```bash
./target/release/spanda control-center serve \
  --program examples/showcase/self_healing/rover.sd

spanda control-center serve \
  --program examples/showcase/self_healing/rover.sd
```

The **Recovery** tab (operator+ roles with `Operate` permission) shows:

| Panel | Content |
|-------|---------|
| **Plans & metrics** | Active recovery plans and aggregated orchestrator metrics |
| **Playbooks & history** | Built-in and plugin playbooks; persisted evidence history |
| **Graph** | Recovery dependency graph (`GET /v1/recovery/graph`) — nodes and edges |
| **Actions** | Plan, simulate, and execute recovery via REST (Bearer token required for execute) |

Same panels ship in `@davalgi-spanda/web` `RecoveryPanel`. API reference:
[recovery-api.md](./recovery-api.md) · SDK: [recovery-sdk.md](./recovery-sdk.md).

### Cognitive & Resilience dashboard

The **Cognitive & Resilience** tab (Health & incidents group; operator+ with `Operate` permission)
organizes live panels by **functional domain**:

| Panel | Functional domain | Content |
|-------|-------------------|---------|
| **Strategic Planning** | Strategic Planning | Governance summary and deployment profiles (`GET /v1/governance`, `/v1/deployment-profiles`) |
| **Reflex Events** | Reflex & Safety | Platform reflex actions and traces (`GET /v1/autonomy/reflex*`) |
| **Attention Queue** | Attention Engine | Prioritized event window (`GET /v1/autonomy/attention`) |
| **Homeostasis** | Homeostasis Engine | Entity stability reports (`GET /v1/autonomy/homeostasis`) |
| **Platform Immunity** | Platform Immunity | Quarantine scan (`GET /v1/autonomy/immunity`) |
| **Operational Memory** | Operational Memory | Memory category refs (`GET /v1/autonomy/memory`) |
| **Damage Risk** | Damage Risk Assessment | Entity `damage_risk` from autonomy profile |
| **Recovery Confidence** | Adaptive Learning | Platform recovery confidence + entity profile |

Per-entity autonomy profiles: `GET /v1/entities/{id}/autonomy`. gRPC parity: `ListAutonomyReflexes`,
`GetAutonomyHomeostasis`, `GetEntityAutonomy`, and related RPCs (proto **1.0.13+**).

**Beta** — live REST panels shipped; promotion to **Stable** follows Control Center P1 backlog in
[ROADMAP.md](../ROADMAP.md). Guide:
[cognitive-resilience-architecture.md](./cognitive-resilience-architecture.md) · Maturity:
[cognitive-resilience-maturity.md](./cognitive-resilience-maturity.md) · Smoke:
`./scripts/cognitive_resilience_smoke.sh`

### Human Interaction dashboard

Launch with the Spatial Computing & Human-Robot Collaboration blueprint:

```bash
./target/release/spanda control-center serve \
  --config examples/solutions/spatial-computing/spanda.toml \
  --program examples/solutions/spatial-computing/warehouse-ar/pick_mission.sd

spanda control-center serve \
  --config examples/solutions/spatial-computing/spanda.toml \
  --program examples/solutions/spatial-computing/warehouse-ar/pick_mission.sd
```

The **Humans** tab shows operator dashboard, wearable inventory, AR/HRI sessions, VR training links,
and health opt-in policy. The same panels ship in `@davalgi-spanda/web` `ControlCenterPanel` (parity
with embedded HTML).

Before promoting Human Interaction to **Stable**: start HRI field soak (`date -u +%Y-%m-%d >
.spanda/hri-field-soak-start.txt`), then run `./scripts/hri_stable_promotion_gate.sh` — see
[stable-hardening-human-interaction.md](./stable-hardening-human-interaction.md).

**Experimental (H4)** — Human Interaction tab and panels:

| Panel | Content |
|-------|---------|
| **Human Dashboard** | Fleet-wide human entities, roles, availability |
| **Operator Dashboard** | Per-operator readiness, certifications, assignments |
| **Wearable Inventory** | Connected wearables, battery, connectivity |
| **AR Session Viewer** | Active AR/XR sessions, spatial anchors |
| **VR Training** | Training session launcher, replay links |
| **Live Collaboration** | Remote expert sessions, participant graph |
| **Mission Collaboration** | Human + robot + drone task allocation |
| **Operator Readiness** | Team readiness rollup, blocking dimensions |
| **Approval Queue** | Mission, recovery, and config approvals (extends E1) |

Planned REST endpoints (experimental — humans/wearables/health and HRI sessions implemented):

| Endpoint | Description |
|----------|-------------|
| `GET /v1/humans` | Human operator list |
| `GET /v1/humans/{id}/readiness` | Per-operator readiness |
| `GET /v1/humans/readiness` | Team readiness rollup (H5) |
| `GET /v1/wearables` | Wearable inventory |
| `GET /v1/human-health/policy` | Health opt-in gate |
| `GET /v1/hri/sessions` | HRI / remote expert sessions |
| `GET /v1/hri/collaboration` | Live collaboration participant graph (H5) |
| `GET /v1/hri/context` | Hazard zones and human location context (H5) |
| `GET /v1/humans/twins` | Human digital twin metadata (H6) |
| `GET /v1/operator/mission/approvals` | Mission approval queue (H6) |
| `POST /v1/hri/sessions/{id}/annotate` | AR annotation publish |

See [solutions/spatial-computing.md](./solutions/spatial-computing.md) ·
[human-interaction-spatial-computing-roadmap.md](./human-interaction-spatial-computing-roadmap.md).

### Smart Spaces dashboard

Launch with the Smart Spaces & Ambient Intelligence blueprint:

```bash
./target/release/spanda control-center serve \
  --config examples/solutions/smart-spaces/spanda.toml \
  --program examples/solutions/smart-spaces/smart-building/floor_readiness.sd

spanda control-center serve \
  --config examples/solutions/smart-spaces/spanda.toml \
  --program examples/solutions/smart-spaces/smart-building/floor_readiness.sd
```

The **Smart Spaces** tab (experimental) shows building and facility operations across residential,
commercial, and campus deployments. See [solutions/smart-spaces.md](./solutions/smart-spaces.md).

**Experimental** — Smart Spaces tab and panels:

| Panel | Content |
|-------|---------|
| **Buildings** | Facility list, floor map, readiness rollup per building |
| **Rooms & zones** | Zone tree, occupancy counts, ambient context |
| **Occupancy** | Presence timeline, flow between zones |
| **Devices** | IoT inventory, gateway primary/backup role, battery quorum |
| **Robots** | Vacuum, service, inspection robot status and missions |
| **Wearables** | Linked wearables, health opt-in indicator |
| **Health** | Device pool health, degraded critical sensors |
| **Readiness** | Pre-mode blockers (night, lockdown, evac) |
| **Trust** | Lock/camera attestation, package trust scores |
| **Energy** | Generation, storage SOC, EV sessions, demand-response events |
| **Security** | Access audit, tamper alerts, lockdown state |
| **Environmental** | CO₂, AQ, temperature baselines vs actuals |
| **Emergency** | Fire, leak, power failure status and active missions |

REST endpoints (experimental, shipped):

| Endpoint | Description |
|----------|-------------|
| `GET /v1/smart-spaces/summary` | Blueprint rollup (facilities, energy, emergency) |
| `GET /v1/smart-spaces/devices` | IoT device inventory (`?facility_id=`) |
| `GET /v1/facilities/{id}/floor-map` | Zone tree floor map |
| `GET /v1/facilities/{id}/security` | Access control and lockdown status |
| `GET /v1/facilities/{id}/health` | Device pool health for facility |
| `GET /v1/zones/{id}/environment` | CO₂, temperature, humidity, AQ |
| `GET /v1/energy/systems/{id}` | SOC, generation, EV session detail |
| `GET /v1/facilities` | Building / facility entity list with zones and gateways |
| `GET /v1/facilities/{id}/readiness` | Per-building readiness rollup |
| `GET /v1/zones/{id}/occupancy` | Zone occupancy twin |
| `GET /v1/energy/systems` | Solar, battery, EV, meter summary |
| `GET /v1/emergency/status` | Active emergency missions and continuity pairs |

Remote CLI:

```bash
spanda control-center smart-spaces summary
spanda control-center smart-spaces facilities
spanda control-center smart-spaces readiness --facility-id tower-demo
spanda control-center smart-spaces occupancy --zone-id floor-12
spanda control-center smart-spaces energy
spanda control-center smart-spaces emergency
```

Generic `api get` also works:

```bash
spanda control-center api get /v1/smart-spaces/summary
spanda control-center api get /v1/facilities/tower-demo/readiness
spanda control-center api get /v1/zones/floor-12/occupancy
```

### Remote CLI (REST parity)

Query a running Control Center without curl — uses `SPANDA_CONTROL_CENTER_URL` (default
`http://127.0.0.1:8080`) and `SPANDA_API_KEY` for mutations:

```bash
export SPANDA_CONTROL_CENTER_URL=http://127.0.0.1:8080
export SPANDA_API_KEY=your-operator-key

spanda control-center status
spanda control-center status --discover
spanda control-center stop
spanda control-center dashboard
spanda control-center drift --baseline-id <snapshot-id>
spanda control-center incidents list
spanda control-center approvals list
spanda control-center approvals submit --snapshot-id <id>
spanda control-center approvals approve <approval-id>
spanda control-center evidence list
spanda control-center sre summary
spanda control-center devices list
spanda control-center readiness run
spanda control-center ota plan --strategy canary --version 1.0 --dry-run
spanda control-center compliance export --profile defense
spanda control-center trust package --name spanda-mqtt

# Generic escape hatch for any /v1 path
spanda control-center api get /v1/sre/summary
spanda control-center api post /v1/ota/plan --body '{"strategy":"canary","version":"1.0","dry_run":true}'
```

---

## Native gRPC (tonic)

`spanda control-center serve --grpc-bind 127.0.0.1:50051` starts a tonic gRPC server alongside REST.

| RPC | Description |
|-----|-------------|
| `Health` | Liveness probe |
| `GetTenant` | Instance tenant scope (`GET /v1/tenant` parity) |
| `GetDashboard` | Dashboard JSON (device pool, fleet agents, alerts) |
| `ListDevices` | Device pool entries (`GET /v1/devices` parity) |
| `ListFleetAgents` | Registered fleet agents (`GET /v1/fleet/agents` parity) |
| `EvaluateReadiness` | Readiness rollup (`POST /v1/readiness/run` parity) |
| `GetSreSummary` | SRE availability rollup (`GET /v1/sre/summary` parity) |
| `ListSreIncidents` | Incident list (`GET /v1/sre/incidents` parity) |
| `CreateSreIncident` | Open incident (`POST /v1/sre/incidents` parity) |
| `GetTrustPackage` | Package trust score (`GET /v1/trust/package` parity) |
| `GetOpenApi` | OpenAPI 3.1 spec JSON (`GET /v1/openapi.json` parity) |
| `GetOtlpMetrics` | OTLP metrics preview (`GET /v1/observability/otlp/metrics`) |
| `GetObservabilityBackend` | Collector URL summary (`GET /v1/observability/backend`) |
| `GetHealthSummary` | Health rollup (`GET /v1/health/summary`) |
| `GetAssuranceSummary` | Assurance policy summary |
| `GetDiagnosisSummary` | Diagnosis policy summary |
| `GetExecutiveScorecard` | Executive scorecard |
| `QueryDigitalThread` | Digital thread query (`query` = URL query string) |
| `GetOtaStatus` | OTA rollout status |
| `DiscoverDevices` | Single-transport discovery (`query` = URL query string) |
| `RunDiscovery` | Multi-transport pool ingest (`POST /v1/devices/discover` parity) |
| `ProvisionDevice` | Provisioning workflow (`POST /v1/provision` parity) |
| `PlanOta` | OTA rollout plan (`POST /v1/ota/plan` parity) |
| `OperatorQuarantine` | Operator quarantine workflow |
| `OperatorMissionApprove` | Mission approval workflow |
| `ExportCompliance` | Compliance export (`query` = `profile=defense`, …) |
| `ListComplianceEvidence` | Append-only evidence log (`GET /v1/compliance/evidence`) |
| `ListConfigApprovals` | Config publish approval queue (`GET /v1/config/approvals`) |
| `SubmitConfigApproval` | Submit snapshot for approval (`POST /v1/config/approvals`) |
| `ApproveConfigApproval` | Approve and publish snapshot (`POST /v1/config/approvals/{id}/approve`) |
| `RejectConfigApproval` | Reject pending approval (`POST /v1/config/approvals/{id}/reject`) |
| `DetectDrift` | Operational drift report (`baseline_id` in request) |
| `ListEntities` | Entity inventory (`GET /v1/entities` parity) |
| `GetEntity` | Entity detail (`GET /v1/entities/{id}` parity) |
| `GetEntityGraph` | Full entity graph (`GET /v1/entities/graph`) |
| `GetEntityTraceability` | Unified traceability (`GET /v1/entities/traceability`) |
| `QueryEntities` | Structured entity query (`POST /v1/entities/query`) |
| `GetEntityRelationships` | Relationship edges (`GET /v1/entities/{id}/relationships`) |
| `GetEntityReadiness` | Readiness snapshot (`GET /v1/entities/{id}/readiness`) |
| `RegisterEntity` | Register entity overlay (`POST /v1/entities/register`; Bearer) |
| `TagEntity` | Add/remove tags (`POST /v1/entities/{id}/tags`; Bearer) |
| `RelateEntities` | Relate entities (`POST /v1/entities/relationships`; Bearer) |
| `SyncEntities` | Sync overlay to TOML (`POST /v1/entities/sync`; Bearer) |
| `GetAnalyticsReadiness` | Readiness trends (`GET /v1/analytics/readiness` parity; `query` = URL query string) |
| `GetAnalyticsWhatIf` | What-if failure scenarios (`GET /v1/analytics/what-if` parity) |
| `GetAnalyticsMissionRisk` | Mission deployment risk (`GET /v1/analytics/mission-risk` parity) |
| `GetAnalyticsReadinessForecast` | Readiness degradation forecast (`GET /v1/analytics/readiness-forecast` parity) |
| `GetAnalyticsTrustGraph` | Trust-weighted dependency graph (`GET /v1/analytics/trust-graph` parity) |

Proto: `crates/spanda-api/proto/spanda/v1/control_center.proto` — package `spanda.v1`. Pin semver
and RPC count via `GET /v1/version` → `grpc.proto_semver` and `grpc.rpc_count` (currently
**1.0.14**, **164** RPCs). gRPC reflection is enabled when tonic-reflection is compiled in.

```bash
# Example with grpcurl (reflection or proto file)
grpcurl -plaintext 127.0.0.1:50051 list
grpcurl -plaintext -import-path crates/spanda-api/proto -proto spanda/v1/control_center.proto \
  -d '{}' 127.0.0.1:50051 spanda.v1.ControlCenter/Health
```

---

## REST API v1

| Endpoint | Method | Auth | Description |
|----------|--------|------|-------------|
| `/v1/health` | GET | — | Liveness |
| `/v1/instance` | GET | — | Runtime status (bind, config/program paths, pool summary, `control_center_ui_version`) |
| `/v1/version` | GET | — | API versioning policy; `control_center_ui_version`, `spanda_version`, gRPC proto metadata |
| `/v1/tenant` | GET | — | Active tenant (`SPANDA_TENANT_ID`) |
| `/v1/dashboard` | GET | — | Device pool summary, fleet agent count, alerts |
| `/v1/devices` | GET | — | Device pool entries |
| `/v1/devices/{id}` | GET | — | Single device record |
| `/v1/devices/{id}` | PATCH | Bearer | Update `lifecycle_state` |
| `/v1/devices/discover` | POST | Bearer | Multi-transport discovery (registers matches) |
| `/v1/devices/{id}/provision` | POST | Bearer | Per-device provision workflow |
| `/v1/devices/{id}/assign` | POST | Bearer | Assign device to robot |
| `/v1/devices/{id}/quarantine` | POST | Bearer | Quarantine device |
| `/v1/devices/{id}/trust` | POST | Bearer (Approve) | Operator trust / approve device |
| `/v1/robots` | GET | — | Robot inventory from device tree |
| `/v1/fleets` | GET | — | Fleet inventory |
| `/v1/device-tree` | GET | — | Hierarchy + logical/physical mapping JSON |
| `/v1/readiness/run` | POST | — | Device readiness impact check |
| `/v1/device-reports` | GET | — | Inventory, trust, calibration reports |
| `/v1/fleet/agents` | GET | — | Registered fleet agents (`.spanda/fleet-agents.json`) |
| `/v1/alerts` | GET | — | Alert history |
| `/v1/alerts/test` | POST | Bearer | Dispatch test alert |
| `/v1/secrets` | GET | Bearer | Secret metadata (no values) |
| `/v1/rbac/matrix` | GET | — | Role permission matrix |
| `/v1/rbac/me` | GET | Bearer | Authenticated operator role and permissions |
| `/v1/provision` | POST | Bearer | Run discover → ready workflow |
| `/v1/discovery` | GET | — | Package-backed discovery (`?transport=mdns` or `subnet`); response includes `tls` policy summary |
| `/v1/config/snapshots` | GET/POST | POST: Bearer | List or save configuration snapshots (`encrypt: true` + `SPANDA_CONFIG_SNAPSHOT_KEY` for AES-256-GCM at rest) |
| `/v1/config/approvals` | GET | — | List config publish approval requests |
| `/v1/config/approvals` | POST | Bearer (Deploy) | Submit approval request for a snapshot (`required_approvals` or `SPANDA_CONFIG_APPROVALS_REQUIRED`) |
| `/v1/config/approvals/{id}/approve` | POST | Bearer (Approve) | Record approver vote; publish when quorum met (`quorum.received` / `quorum.required`) |
| `/v1/config/approvals/{id}/reject` | POST | Bearer (Approve) | Reject a pending config publish |
| `/v1/health/summary` | GET | — | Device pool health rollup |
| `/v1/assurance/summary` | GET | — | Assurance policy from resolved config |
| `/v1/diagnosis/summary` | GET | — | Diagnosis policy from resolved config |
| `/v1/openapi.json` | GET | — | OpenAPI 3.1 specification |
| `/v1/drift` | GET | — | Operational drift vs baseline snapshot (`?baseline_id=`) |
| `/v1/drift/scans` | GET | — | Recorded drift scan history |
| `/v1/drift/scan` | POST | Bearer | Trigger drift scan (optional `baseline_id`) |
| `/v1/ota/plan` | POST | Bearer | Plan canary / staged / blue_green rollout; `require_certify` enforced when `SPANDA_OTA_REQUIRE_CERTIFY` or `SPANDA_PRODUCTION_POLICY=production` |
| `/v1/ota/execute` | POST | Bearer | Execute rollout; `rollback_on_readiness_fail` gates post-deploy readiness; certification proof required in production policy |
| `/v1/ota/status` | GET | — | OTA deploy state (`.spanda/deploy-state.json`) |
| `/v1/trust/package` | GET | — | Package trust evaluation (`?name=&version=`) |
| `/v1/sre/summary` | GET | — | Availability, incidents, MTTR/MTBF hints, `health_trends`, `readiness_trends`, `slo`, and `burn_rate` |
| `/v1/sre/incidents` | GET | — | Incident list |
| `/v1/sre/incidents` | POST | Bearer | Open incident |
| `/v1/sre/incidents/{id}/ack` | POST | Bearer | Acknowledge incident |
| `/v1/sre/incidents/{id}/resolve` | POST | Bearer | Resolve incident |
| `/v1/integrations/pagerduty/webhook` | POST | Bearer | Inbound PagerDuty ack/resolve → incident workflow sync |
| `/v1/observability/traces` | GET | — | Recent API trace records |
| `/v1/observability/otlp/traces` | GET | — | OTLP/JSON trace preview for Jaeger |
| `/v1/observability/otlp/metrics` | GET | — | OTLP/JSON metrics preview |
| `/v1/observability/backend` | GET | — | Configured OTLP collector endpoints |
| `/v1/observability/otlp/export` | POST | Bearer | Push API traces to OTLP collector |
| `/v1/stream/telemetry` | WebSocket | — | Live telemetry, traces, and alerts |
| `/v1/operator/quarantine` | POST | Bearer | Quarantine a device |
| `/v1/operator/mission/approve` | POST | Bearer | Approve or reject a mission |
| `/v1/rpc` | POST | — | gRPC-compatible JSON gateway |
| **gRPC (tonic)** | — | — | Native `ControlCenter` service on `--grpc-bind` (see `GET /v1/version` → `grpc.rpc_count`; full REST parity except JSON-RPC gateway) |
| `/v1/compliance/export` | GET/POST | Bearer | Accreditation bundle (`?profile=defense`, `iso26262`, …); appends immutable evidence log |
| `/v1/compliance/profiles` | GET | — | Signed profile catalog (defense, medical, iso26262, iso13849, iec61508) with Ed25519 verification |
| `/v1/compliance/evidence` | GET | Bearer | List append-only compliance evidence records |
| `/v1/digital-thread/query` | GET | — | Trace chain (`?capability=`, `?device_id=`, `?lifecycle_phase=`) |
| `/v1/executive/scorecard` | GET | — | Mission scorecard rollup |
| `/v1/analytics/readiness` | GET | — | Readiness trends and forecast |
| `/v1/analytics/what-if` | GET | `scenario`, `all` | What-if failure scenario analysis |
| `/v1/analytics/mission-risk` | GET | — | Mission deployment risk score |
| `/v1/analytics/readiness-forecast` | GET | `horizon`, `all` | Readiness degradation forecast |
| `/v1/analytics/trust-graph` | GET | `format` | Trust-weighted dependency graph |
| `/v1/reports/export` | GET | Bearer | Combined report (`format=markdown`, `json`, or `pdf`) |
| `/v1/reports/schedules` | GET | Bearer | List scheduled report delivery jobs |
| `/v1/reports/schedules` | POST | Bearer | Create scheduled webhook delivery (`profile`, `format`, `destination_url`, `interval_hours`) |
| `/v1/audit/mutations` | GET | Bearer | Hash-chained mutation audit trail |
| `/v1/audit/mutations/export` | GET | Bearer | SIEM export (`?format=cef` or `jsonl`) |

Authenticate mutations with `Authorization: Bearer <token>`. See [Authentication & API
keys](#authentication--api-keys) for how to create and configure tokens.

**HA persistence:** Alert history and API trace log hydrate from
`.spanda/control-center-alerts.json` and `.spanda/control-center-traces.json` on startup (override
directory with `SPANDA_CONTROL_CENTER_STATE_DIR`).

**API versioning:** `GET /v1/version` documents supported versions and returns
`control_center_ui_version` (UI semver) and `spanda_version` (platform build). Clients may send
`X-Spanda-Api-Version: v1`; unsupported values return `400`. Breaking changes ship under a new
`/v2/` path prefix. Operator CLI: `spanda control-center --version`. See
[control-center-versioning.md](./control-center-versioning.md).

**Rate limiting:** Set `SPANDA_API_RATE_LIMIT_PER_MINUTE` (per API key, or `anonymous` when
unauthenticated). Excess requests return HTTP `429` with `Retry-After` (REST) or gRPC
`RESOURCE_EXHAUSTED`.

**Mutation audit:** Successful `POST`/`PATCH`/`PUT`/`DELETE` requests append hash-chained audit
records (`GET /v1/audit/mutations`, Bearer required). Export for SIEM: `GET
/v1/audit/mutations/export?format=cef|jsonl`. Persisted to `.spanda/control-center-mutations.jsonl`
(override with `SPANDA_MUTATION_AUDIT_PATH`).

**Production policy:** Set `SPANDA_PRODUCTION_POLICY=production` to enable fleet defaults: OTA
certification required (`SPANDA_OTA_REQUIRE_CERTIFY`), discovery TLS required
(`SPANDA_DISCOVERY_REQUIRE_TLS`). See
[stable-hardening-enterprise-ops.md](./stable-hardening-enterprise-ops.md).

**Scheduled reports:** Background delivery when `SPANDA_REPORT_SCHEDULE_INTERVAL_SECS` is set (e.g.
`3600`). Schedules persist under `SPANDA_CONTROL_CENTER_STATE_DIR`.

**Discovery TLS:** `SPANDA_DISCOVERY_REQUIRE_TLS` blocks insecure `http://` / `mqtt://` endpoints;
`SPANDA_DISCOVERY_TLS_CA_BUNDLE` points at vendor CA PEM. Registry package: `spanda-discovery-tls`.

**SRE burn monitor:** Fast-burn alerts when `SPANDA_SRE_BURN_SCAN_INTERVAL_SECS` > 0
(`SPANDA_SRE_BURN_RATE_FAST`, `SPANDA_SRE_BURN_WINDOW_HOURS`).

**Drift scans:** Background scheduler when `SPANDA_DRIFT_SCAN_INTERVAL_SECS` > 0.

Pass optional `X-Correlation-ID` on any request; the server echoes it on the response and records
traces for `/v1/observability/traces`.

Govern-and-trace endpoints require a loaded program:

```bash
./target/release/spanda control-center serve --config spanda.toml --program rover.sd
spanda control-center serve --config spanda.toml --program rover.sd
```

---

## Control Center UI sections

The embedded HTML at `/` and the `@davalgi-spanda/web` Control Center panel share the same
operational tabs. After signing in with a Bearer token, `GET /v1/rbac/me` drives **role-aware
navigation**: each role (administrator, supervisor, developer, operator, safety_officer, auditor,
guest) sees a tailored tab set, dashboard workspace panel, per-tab hints, and action buttons gated
by RBAC permissions (`Deploy`, `Operate`, `Approve`, `Provision`, etc.).

The `@davalgi-spanda/web` Control Center panel includes:

| Section | Purpose |
|---------|---------|
| Dashboard | Pool summary, alerts, fleet agents |
| Devices | Device pool with lifecycle and assignment |
| Fleet | Fleets, robots, agents |
| Discovery | Multi-transport device discovery |
| Provisioning | Device inspect and provision workflows |
| Mapping | Logical ↔ physical mapping export |
| Health | Pool health rollup |
| Readiness | Readiness impact check |
| Compliance | Signed profile picker + accreditation export + evidence log |
| Digital Thread | Interactive capability→device graph with filters |
| Traceability | Trust and identity trace view |
| Recovery | Orchestrator plans, metrics, playbooks, history, graph, plan/simulate/execute |
| Cognitive & Resilience | Functional domain panels — reflex, attention, homeostasis, immunity, memory, damage risk (`/v1/autonomy/*`) |
| Administration | API keys, users, alert channels, integrations (administrator) |
| Simulation | Program sim execute with decision traces |
| Replay | Trace library, deterministic replay |
| **Telemetry** | Live WebSocket stream (`/v1/stream/telemetry`) |
| **Trends** | Readiness forecast charts and SRE trend rollup |
| **Continuity** | Fleet-agent continuity poll and mission pause/resume/cancel |
| **Fleet map** | Grid/geospatial markers (`GET /v1/fleet/map`) |
| **Reports** | Scheduled report CRUD and compliance preview |
| **Playground** | In-browser WASM check/run |
| **Marketplace** | Installed plugin and Control Center panel browser |
| **Chaos** | Fault injection catalog and simulation (`/v1/chaos/*`) |

**Enhancements on existing tabs:** SRE observability bridge (Grafana embed when `SPANDA_GRAFANA_URL`
is set, Jaeger/trace links), config change history + deploy gate modal, bulk device
quarantine/trust, OTA rollout progress, decision policy editor, Smart Spaces floor-plan SVG,
analytics trust graph + certification pack download, Twin Cloud usage meters, OIDC/Slack OAuth
wizards with callback landing pages (`/admin/oauth/*`) and PKCE, offline dashboard cache, plugin
bundle loader, Humans collaboration graph + AR annotate, entity graph default view, saved connection
profile switcher, Discovery transport wizards, desktop `spawn_control_center_api` + native
notifications + system tray.

**New REST APIs:** `GET /v1/fleet/map`, `GET /v1/config/history`, `GET /v1/deploy/gate`, `GET|PUT
/v1/admin/oidc`, `POST /v1/admin/oidc/sync`, `GET|POST /v1/admin/slack`, `GET /v1/chaos/injections`,
`POST /v1/chaos/simulate`. **gRPC server reflection** enabled (tonic-reflection).

---

## Python SDK

```bash
pip install -e packages/sdk-python
export SPANDA_API_KEY=your-key
python -c "from spanda_sdk import ControlCenterClient; print(ControlCenterClient().health())"
```

Integration tests: `SPANDA_SDK_INTEGRATION=1 SPANDA_CONTROL_CENTER_URL=http://127.0.0.1:8080 pytest
packages/sdk-python/tests`

WebSocket streaming (`pip install -e 'packages/sdk-python[stream]'`):

```python
from spanda_sdk import TelemetryStream
TelemetryStream("http://127.0.0.1:8080").wait_for_hello()
```

### OTLP traces (Jaeger)

Control Center API spans export as OTLP/JSON traces:

```bash
export SPANDA_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces
export SPANDA_OTLP_TRACE_AUTO_PUSH=1   # optional: push each API span
curl -H "Authorization: Bearer $SPANDA_API_KEY" \
  -X POST "http://127.0.0.1:8080/v1/observability/otlp/export"
```

Preview payload: `GET /v1/observability/otlp/traces`

Live telemetry WebSocket: `ws://127.0.0.1:8080/v1/stream/telemetry` (hello + telemetry/trace/alert
events)

---

## Device Pool lifecycle

Devices in `[[devices]]` or the device tree carry optional lifecycle fields:

| State | Meaning |
|-------|---------|
| `discovered` | Seen but not verified |
| `quarantined` | Blocked pending review |
| `verified` | Identity and trust checks passed |
| `assigned` | Bound to a robot |
| `active` | Operational (`healthy` is an alias) |
| `degraded` / `offline` / `failed` | Runtime posture |
| `retired` | Removed from active pool |

Set in TOML:

```toml
[[devices]]
id = "lidar-front"
type = "lidar"
lifecycle_state = "active"
assigned_robot = "rover-1"
logical_name = "front_lidar"
```

See [device-pool.md](./device-pool.md) and [device-provisioning.md](./device-provisioning.md).

---

## Alerting

Configure delivery channels via environment variables:

| Variable | Effect |
|----------|--------|
| `SPANDA_ALERT_WEBHOOK_URL` | POST JSON alert payload |
| `SPANDA_ALERT_EMAIL_TO` | Email recipient (logs if `SPANDA_SMTP_HOST` unset) |
| `SPANDA_ALERT_EMAIL_DRY_RUN=1` | Log email without sending |
| `SPANDA_ALERT_DEDUP_WINDOW_*_SECS` | Per-severity dedup windows before dispatch |
| `SPANDA_PAGERDUTY_WEBHOOK_SECRET` | Optional HMAC for inbound PagerDuty webhook |

Registry packages: `spanda-alert-pagerduty` (bi-directional sync), `spanda-alert-escalation`,
`spanda-alert-slack`, `spanda-alert-teams`.

Default: log to stderr.

---

## Smoke test

```bash
./scripts/enterprise_ops_smoke.sh
./scripts/control_center_desktop_smoke.sh
./scripts/security_audit_prep.sh      # third-party audit intake
./scripts/field_soak_gate.sh          # after 30-day pilot start date
./scripts/verify_sdk_publish_ready.sh # PyPI + npm pack readiness
./scripts/verify_desktop_release_ready.sh # desktop version sync + Tauri compile # PyPI + npm pack readiness
```

---

## Stable promotion

Enterprise operations E1–E4 are **Stable** with full stable-hardening checklist items **shipped in
code**. SDKs **0.4.2** (registry) and Control Center desktop **0.6.3** (`desktop-v0.6.3`, with
sidebar/CLI/API version display) are published via registry tags and GitHub Releases. Remaining
organizational gates: third-party security audit sign-off and 30-day field soak. See
[stable-hardening-enterprise-ops.md](./stable-hardening-enterprise-ops.md) ·
[field-soak-gate.md](./field-soak-gate.md) ·
[security-audit-third-party.md](./security-audit-third-party.md) ·
[desktop-release-runbook.md](./desktop-release-runbook.md) ·
[control-center-versioning.md](./control-center-versioning.md).

---

## Versioning

Control Center has an independent **desktop release stream** (`desktop-v*`) and a **UI semver**
shown in the sidebar, CLI, and API.

| Surface | How to read the version |
|---------|-------------------------|
| UI sidebar | `vX.Y.Z` under the Control Center title |
| CLI | `spanda control-center --version` · `spanda control-center status` → `UI version` |
| API | `GET /v1/version` → `control_center_ui_version` · `GET /v1/instance` |
| Serve startup | `Spanda Control Center vX.Y.Z listening on …` |

**Automatic bump:** PRs labeled `release:patch|minor|major` that change Control Center paths trigger
a matching desktop bump after CI Integration on `main`. **Manual:** `python3 scripts/bump_version.py
minor --stream desktop`.

Full policy, path list, and troubleshooting:
**[control-center-versioning.md](./control-center-versioning.md)** · release runbook:
[desktop-release-runbook.md](./desktop-release-runbook.md).

---

## Desktop (Tauri)

Package: `@spanda/control-center-desktop` (`packages/control-center-desktop`).

1. Start the API:
   - `./target/release/spanda control-center serve --bind 127.0.0.1:8080` (release build), or
   - `spanda control-center serve --bind 127.0.0.1:8080` (installed)
2. Dev shell: `npm run control-center:desktop:dev` (Vite on port **5174**)
3. Optional API URL: `VITE_CONTROL_CENTER_URL=http://host:port`

The desktop shell reuses `ControlCenterPanel` from `@davalgi-spanda/web`; it does not embed
`spanda-api`. Production installers ship via **`desktop-v*`** tags →
`.github/workflows/desktop-release.yml` → GitHub Release (macOS `.dmg` / `.app.tar.gz`) plus
workflow artifacts. Pre-flight: `./scripts/verify_desktop_release_ready.sh`. Optional
codesign/notarize: `./scripts/sign_tauri_macos.sh` when Apple secrets are set. See
[packages/control-center-desktop/README.md](../packages/control-center-desktop/README.md) ·
[desktop-release-runbook.md](./desktop-release-runbook.md) ·
[control-center-versioning.md](./control-center-versioning.md).

**Current release:** **0.6.3** — tag
[`desktop-v0.6.3`](https://github.com/Davalgi/Spanda/releases/tag/desktop-v0.6.3) on GitHub
Releases.

---

## Status

**Stable** (Phase E1–E4). Includes device pool provisioning, multi-transport discovery with
production TLS policy, WebSocket telemetry streaming, OTLP trace/metrics export, SLO burn-rate
monitor, PagerDuty bi-directional sync, compliance export with **signed profile catalog**, scheduled
report delivery, digital thread query with **full lifecycle graph UI**, executive scorecard, report
composer (including PDF), Grafana dashboard templates (`spanda-grafana-dashboards`), official SDKs
**0.4.2**, and Tauri desktop **0.6.3** production release (`desktop-v0.6.3`) with sidebar/CLI/API
version display.
