From bba7065d6572a262c2423fd87c75889b10f50e50 Mon Sep 17 00:00:00 2001 From: David Pierron Date: Sat, 11 Jul 2026 15:06:25 +0200 Subject: [PATCH] docs: sync guide/technical docs to the capability-aware bridge (LOT-018) Update the remaining docs that still described the pre-LOT-018 model: - configuration (EN+FR): Bearer token + role model, dcs-tokens.yaml, no-key /config.json, ephemeral WS tickets; drop the obsolete "API key exposure" warning. - cli-reference (EN+FR): web client keeps the token server-side, hands tickets. - architecture/index (EN+FR): Bearer role-based auth (not X-API-Key), capability -aware actions. - README: fix the ASCII diagram (port 7777, role-based Bearer auth) and the MCP tool list (catalogue-driven). Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 1 + README.md | 12 +++---- docs/guide/cli-reference.en.md | 7 ++-- docs/guide/cli-reference.fr.md | 8 +++-- docs/guide/configuration.en.md | 53 +++++++++++++++++++++------- docs/guide/configuration.fr.md | 57 +++++++++++++++++++++++-------- docs/index.en.md | 2 +- docs/index.fr.md | 2 +- docs/technical/architecture.en.md | 2 +- docs/technical/architecture.fr.md | 2 +- 10 files changed, 103 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42059b7..88c56b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - fix(client): replace `typer.Option(Path(...))` with `typer.Option(default=...)` in all three subcommands to prevent the same Typer OptionInfo-as-default bug (LOT-011 / BUGF-002) ### Added +- docs: sync user/technical docs to the capability-aware bridge — Bearer + role-based auth and `dcs-tokens.yaml` in configuration, no-credential-in-browser web flow, catalogue-driven MCP tool list, corrected architecture/index/README (dropped stale `X-API-Key`/port `9999`/old MCP tool references) (LOT-018) - feat(clients): catalogue-driven MCP + TUI/WEB catalogue presentation (ADR-0005 / LOT-018 ticket 08) — the MCP client now exposes a small, fixed tool set independent of catalogue size (`list_catalog`/`search_catalog`/`describe_action` discovery, a single generic `run_action`, `get_units`/`capabilities` reads, and `exec_lua` gated to `superuser`), retiring the ad-hoc per-action `spawn`/`spawn_unit`/`get_mission_info` tools. The TUI lists the mission's available actions on startup; the WEB client shows a capability-filtered actions panel fed by a new `/catalog` proxy (token stays server-side). Clients hold no domain knowledge — they proxy `/api/catalog` and `/api/action`. CLI reference updated (EN + FR) - feat(transport): Bearer auth + ephemeral WebSocket tickets (ADR-0005 / LOT-018 ticket 07) — **breaking**: REST now authenticates via `Authorization: Bearer ` only (dropped the `X-API-Key` header and the `api_key` query param, so no credential appears in a URL or log). The WebSocket is opened with a single-use ~10 s ticket from the new `POST /api/ws-ticket` (`serve/security.py` `TicketStore`); `/ws/stream` takes `?ticket=` instead of `?api_key=`. The WEB client holds the durable token server-side, serves only host/port at `/config.json` (no key) and proxies tickets via `POST /ws-ticket`; the Leaflet page fetches a ticket before each connect — the key-in-URL leak is gone. MCP and TUI clients migrated to Bearer (+ ticket for the TUI stream). API reference updated (EN + FR) - feat(serve): role-based security — roles, tokens, per-action enforcement (ADR-0005 / LOT-018 ticket 06) — new `serve/security.py` with roles `observer(0)/pilot(1)/operator(10)/administrator(90)/superuser(99)` aligned on VEAF levels, and a role-bearing token store (`{token, role, label, ucid?, expiry?}`, loaded from `dcs-tokens.yaml`) that replaces the single API key. Enforcement lives in the bridge: each action's minimum role is checked before execution (`403` below minimum), `POST /api/exec` is gated to `superuser`, read-only routes to `observer`, `/api/spawn` to `operator`. The caller's resolved VEAF level is propagated into VEAF-backed adapters (never a blanket `bypassSecurity`). Delegated WEB mode resolves UCID→role from `veaf-pilots.txt` server-side. The legacy `api_key` keeps working as a `superuser` token during the transition. API reference updated (EN + FR) diff --git a/README.md b/README.md index 0505c81..825f099 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ Generic bridge between DCS World and external consumers — TUI, web map, and AI ``` DCS World (Lua 5.1) │ dcs-bridge.lua — unit positions + events - │ TCP JSON (port 9999) + │ TCP JSON (port 7777) ▼ dcs-serve (Python asyncio) - │ in-memory snapshot · command bus · X-API-Key auth + │ snapshot · command bus · capability-aware actions · role-based auth (Bearer) ├─── HTTP REST ──► dcs-client tui (Textual terminal UI) ├─── WebSocket ──► dcs-client web (Leaflet map, real-time) └─── HTTP REST ──► dcs-client mcp (MCP stdio → AI agents) @@ -23,10 +23,10 @@ dcs-serve (Python asyncio) ## Features - **Lua bridge** — non-blocking, exponential reconnect, full refresh every 5 s, `exec` and `spawn` commands -- **dcs-serve** — asyncio TCP + FastAPI, snapshot with staleness detection, command/response correlation -- **dcs-client tui** — Textual terminal UI: real-time unit table + Lua REPL -- **dcs-client web** — Leaflet map with coalition-coloured markers and hover tooltips -- **dcs-client mcp** — MCP server exposing `exec_lua`, `get_units`, `spawn_unit`, `get_mission_info` +- **dcs-serve** — asyncio TCP + FastAPI, snapshot with staleness detection, command/response correlation, **capability-aware semantic actions** (façade over DCS/MIST/CTLD/VMCT) with role-based auth +- **dcs-client tui** — Textual terminal UI: real-time unit table + Lua REPL + available-actions catalogue +- **dcs-client web** — Leaflet map with coalition-coloured markers, hover tooltips, and a capability-filtered actions panel +- **dcs-client mcp** — catalogue-driven MCP server: `list_catalog`/`search_catalog`/`describe_action`, a generic `run_action`, `get_units`/`capabilities`, and `exec_lua` (superuser) - **Packaged** — one-file Windows executables via PyInstaller, also available on PyPI ## Quick start diff --git a/docs/guide/cli-reference.en.md b/docs/guide/cli-reference.en.md index 3cda032..13211de 100644 --- a/docs/guide/cli-reference.en.md +++ b/docs/guide/cli-reference.en.md @@ -54,9 +54,10 @@ Options: --help Show this message and exit ``` -Launches a local HTTP server and opens the Leaflet map in the browser. The map connects -to `dcs-serve` using the `host`, `port` and `api_key` from `dcs-client.yaml` (served to -the browser via `GET /config.json`), so no manual URL editing is needed. +Launches a local HTTP server and opens the Leaflet map in the browser. The web server +reads `host`, `port` and `api_key` from `dcs-client.yaml`; it keeps the token +server-side and serves only `host`/`port` at `GET /config.json`, handing the browser +short-lived WebSocket tickets via `POST /ws-ticket` (no credential in the browser). ### dcs-client mcp diff --git a/docs/guide/cli-reference.fr.md b/docs/guide/cli-reference.fr.md index 2a6086c..a170614 100644 --- a/docs/guide/cli-reference.fr.md +++ b/docs/guide/cli-reference.fr.md @@ -54,9 +54,11 @@ Options: --help Afficher ce message et quitter ``` -Lance un serveur HTTP local et ouvre la carte Leaflet dans le navigateur. La carte se -connecte à `dcs-serve` avec les valeurs `host`, `port` et `api_key` de `dcs-client.yaml` -(exposées au navigateur via `GET /config.json`), sans aucune URL à modifier à la main. +Lance un serveur HTTP local et ouvre la carte Leaflet dans le navigateur. Le serveur web +lit `host`, `port` et `api_key` depuis `dcs-client.yaml` ; il conserve le token côté +serveur et n'expose que `host`/`port` via `GET /config.json`, en fournissant au +navigateur des tickets WebSocket éphémères via `POST /ws-ticket` (aucun credential dans +le navigateur). ### dcs-client mcp diff --git a/docs/guide/configuration.en.md b/docs/guide/configuration.en.md index d0b5ab7..62ec3da 100644 --- a/docs/guide/configuration.en.md +++ b/docs/guide/configuration.en.md @@ -13,9 +13,13 @@ tcp_port: 7777 http_host: "0.0.0.0" http_port: 8080 -# API key (auto-generated on first launch if empty) +# Bearer token (auto-generated on first launch if empty). Kept as a superuser +# token during the transition to the role-based model (see dcs-tokens.yaml). api_key: "" +# Path to the role-bearing token store (optional) +tokens_file: "dcs-tokens.yaml" + # Default Lua command timeout, in seconds default_timeout: 10.0 @@ -30,10 +34,29 @@ stale_threshold: 15.0 |---|---|---| | `tcp_port` | `7777` | TCP port the Lua script must target | | `http_port` | `8080` | REST API and WebSocket port | -| `api_key` | *(auto)* | Key to pass to clients (`X-API-Key`) | +| `api_key` | *(auto)* | Durable Bearer token; used as a `superuser` token (ADR-0005) | +| `tokens_file` | `dcs-tokens.yaml` | Optional role-bearing token store | | `default_timeout` | `10.0` | Timeout for `exec` and `spawn` commands | | `stale_threshold` | `15.0` | Delay before marking the snapshot as stale | +### dcs-tokens.yaml (optional) + +Role-bearing tokens replace the single API key (ADR-0005). Each token carries a +role (`observer`/`pilot`/`operator`/`administrator`/`superuser`, or a numeric VEAF +level) and optional `label`/`ucid`/`expiry`: + +```yaml +- token: "observer-token" + role: observer + label: "read-only dashboard" +- token: "ops-token" + role: operator + label: "mission operator" +``` + +REST clients send their token as `Authorization: Bearer `. If this file is +absent, the `api_key` above keeps working as a `superuser` token. + ## dcs-client.yaml This file configures the clients (TUI, web, MCP). @@ -43,23 +66,27 @@ This file configures the clients (TUI, web, MCP). host: "127.0.0.1" port: 8080 -# API key (must match dcs-serve.yaml) -api_key: "your-api-key" +# Bearer token (must match a token accepted by dcs-serve) +api_key: "your-token" # Local port for the web client's Leaflet map web_port: 8081 ``` The file is looked up in the current directory or via the `--config` option. - -`dcs-client web` uses `host`, `port` and `api_key` to connect the Leaflet map to -`dcs-serve`: it serves them to the browser at `GET /config.json`, so filling this file -is enough — no manual URL editing. `web_port` is the local port the map is served on. - -!!! warning "API key exposure" - `/config.json` returns the API key to any client that can reach `web_port`. The web - client binds to `127.0.0.1` by default; only pass `--web-host 0.0.0.0` on a trusted - network. +`api_key` is the client's durable Bearer token; its role determines which actions +succeed (enforced by dcs-serve). + +`dcs-client web` holds this token **server-side** and never exposes it to the +browser (ADR-0005): `GET /config.json` returns only `host`/`port`, and the Leaflet +page opens its WebSocket with a short-lived single-use ticket obtained from the web +server's `POST /ws-ticket` proxy. No credential appears in the page, a URL, or +`/config.json`. `web_port` is the local port the map is served on. + +!!! note "No credential in the browser" + The durable token stays on the web server; the browser only ever receives + ephemeral WebSocket tickets. The web client binds to `127.0.0.1` by default; + only pass `--web-host 0.0.0.0` on a trusted network. ## Lua script diff --git a/docs/guide/configuration.fr.md b/docs/guide/configuration.fr.md index d02e97a..de3d9b5 100644 --- a/docs/guide/configuration.fr.md +++ b/docs/guide/configuration.fr.md @@ -13,9 +13,14 @@ tcp_port: 7777 http_host: "0.0.0.0" http_port: 8080 -# Clé API (générée automatiquement au premier lancement si vide) +# Token Bearer (généré automatiquement au premier lancement si vide). Conservé +# comme token superuser pendant la transition vers le modèle par rôles +# (voir dcs-tokens.yaml). api_key: "" +# Chemin du magasin de tokens porteurs de rôle (optionnel) +tokens_file: "dcs-tokens.yaml" + # Timeout par défaut des commandes Lua, en secondes default_timeout: 10.0 @@ -30,10 +35,30 @@ stale_threshold: 15.0 |---|---|---| | `tcp_port` | `7777` | Port TCP que le script Lua doit cibler | | `http_port` | `8080` | Port de l'API REST et WebSocket | -| `api_key` | *(auto)* | Clé à transmettre aux clients (`X-API-Key`) | +| `api_key` | *(auto)* | Token Bearer durable ; utilisé comme token `superuser` (ADR-0005) | +| `tokens_file` | `dcs-tokens.yaml` | Magasin de tokens porteurs de rôle (optionnel) | | `default_timeout` | `10.0` | Timeout des commandes `exec` et `spawn` | | `stale_threshold` | `15.0` | Délai avant de marquer le snapshot comme périmé | +### dcs-tokens.yaml (optionnel) + +Les tokens porteurs de rôle remplacent la clé API unique (ADR-0005). Chaque token +porte un rôle (`observer`/`pilot`/`operator`/`administrator`/`superuser`, ou un +niveau VEAF numérique) et, en option, `label`/`ucid`/`expiry` : + +```yaml +- token: "observer-token" + role: observer + label: "tableau de bord lecture seule" +- token: "ops-token" + role: operator + label: "opérateur de mission" +``` + +Les clients REST envoient leur token via `Authorization: Bearer `. Si ce +fichier est absent, l'`api_key` ci-dessus continue de fonctionner comme token +`superuser`. + ## dcs-client.yaml Ce fichier configure les clients (TUI, web, MCP). @@ -43,24 +68,28 @@ Ce fichier configure les clients (TUI, web, MCP). host: "127.0.0.1" port: 8080 -# Clé API (doit correspondre à celle de dcs-serve.yaml) -api_key: "votre-cle-api" +# Token Bearer (doit correspondre à un token accepté par dcs-serve) +api_key: "votre-token" # Port local de la carte Leaflet du client web web_port: 8081 ``` Le fichier est recherché dans le répertoire courant ou via l'option `--config`. - -`dcs-client web` utilise `host`, `port` et `api_key` pour connecter la carte Leaflet à -`dcs-serve` : ces valeurs sont exposées au navigateur via `GET /config.json`, donc -remplir ce fichier suffit — aucune URL à modifier à la main. `web_port` est le port -local sur lequel la carte est servie. - -!!! warning "Exposition de la clé API" - `/config.json` renvoie la clé API à tout client capable d'atteindre `web_port`. Le - client web écoute sur `127.0.0.1` par défaut ; ne passez `--web-host 0.0.0.0` que sur - un réseau de confiance. +`api_key` est le token Bearer durable du client ; son rôle détermine quelles +actions aboutissent (appliqué par dcs-serve). + +`dcs-client web` conserve ce token **côté serveur** et ne l'expose jamais au +navigateur (ADR-0005) : `GET /config.json` ne renvoie que `host`/`port`, et la page +Leaflet ouvre son WebSocket avec un ticket éphémère à usage unique obtenu via le +proxy `POST /ws-ticket` du serveur web. Aucun credential n'apparaît dans la page, +une URL ou `/config.json`. `web_port` est le port local sur lequel la carte est +servie. + +!!! note "Aucun credential dans le navigateur" + Le token durable reste sur le serveur web ; le navigateur ne reçoit que des + tickets WebSocket éphémères. Le client web écoute sur `127.0.0.1` par défaut ; + ne passez `--web-host 0.0.0.0` que sur un réseau de confiance. ## Script Lua diff --git a/docs/index.en.md b/docs/index.en.md index 35cdf74..97be774 100644 --- a/docs/index.en.md +++ b/docs/index.en.md @@ -25,7 +25,7 @@ graph LR ## Features - **Lua bridge** — injected into DCS World, streams unit positions and events in real time -- **dcs-serve** — TCP/HTTP/WebSocket server, in-memory snapshot, API key authentication +- **dcs-serve** — TCP/HTTP/WebSocket server, in-memory snapshot, capability-aware actions, role-based (Bearer) authentication - **dcs-client tui** — Textual terminal UI with real-time unit table and Lua REPL - **dcs-client web** — Leaflet map with coalition-coloured markers, real-time updates - **dcs-client mcp** — MCP server exposing `exec_lua`, `get_units`, `spawn_unit`, `get_mission_info` diff --git a/docs/index.fr.md b/docs/index.fr.md index b4f61b3..ecdd6f1 100644 --- a/docs/index.fr.md +++ b/docs/index.fr.md @@ -25,7 +25,7 @@ graph LR ## Fonctionnalités - **Bridge Lua** — injecté dans DCS World, envoie les positions des unités et les événements en temps réel -- **dcs-serve** — serveur TCP/HTTP/WebSocket, snapshot en mémoire, authentification par clé API +- **dcs-serve** — serveur TCP/HTTP/WebSocket, snapshot en mémoire, actions capability-aware, authentification par rôle (token Bearer) - **dcs-client tui** — interface terminal Textual avec tableau des unités en temps réel et REPL Lua - **dcs-client web** — carte Leaflet avec marqueurs colorés par coalition, mise à jour en temps réel - **dcs-client mcp** — serveur MCP exposant `exec_lua`, `get_units`, `spawn_unit`, `get_mission_info` diff --git a/docs/technical/architecture.en.md b/docs/technical/architecture.en.md index 7d53d6f..5806ec2 100644 --- a/docs/technical/architecture.en.md +++ b/docs/technical/architecture.en.md @@ -58,7 +58,7 @@ Python asyncio server with two services co-located in the same event loop: | `Snapshot` | In-memory cache of active units, staleness detection | | `CommandBus` | Command/response correlation by `id` with timeout | | `EventBroadcaster` | Thread-safe fan-out of events to WebSocket clients | -| `FastAPI app` | REST routes + WebSocket, X-API-Key middleware | +| `FastAPI app` | REST routes + WebSocket, Bearer-token role-based auth | ### dcs-client diff --git a/docs/technical/architecture.fr.md b/docs/technical/architecture.fr.md index e31890d..804f8ec 100644 --- a/docs/technical/architecture.fr.md +++ b/docs/technical/architecture.fr.md @@ -58,7 +58,7 @@ Serveur asyncio Python avec deux services co-localisés dans la même boucle d' | `Snapshot` | Cache en mémoire des unités actives, détection de péremption | | `CommandBus` | Corrélation commande/réponse par `id` avec timeout | | `EventBroadcaster` | Fan-out thread-safe des événements vers les WebSocket clients | -| `FastAPI app` | Routes REST + WebSocket, middleware X-API-Key | +| `FastAPI app` | Routes REST + WebSocket, auth par rôle via token Bearer | ### dcs-client