coffre ObsiGate Phase 4 (issue #22) : docs/coffre-reference-flowdeck.md
FlowDeck CI / lint (push) Failing after 2s
FlowDeck CI / test (push) Successful in 11m19s
FlowDeck CI / docker (push) Failing after 2m5s
FlowDeck CI / lint (pull_request) Failing after 0s
FlowDeck CI / test (pull_request) Failing after 0s
FlowDeck CI / docker (pull_request) Skipped
FlowDeck CI / lint (push) Failing after 2s
FlowDeck CI / test (push) Successful in 11m19s
FlowDeck CI / docker (push) Failing after 2m5s
FlowDeck CI / lint (pull_request) Failing after 0s
FlowDeck CI / test (pull_request) Failing after 0s
FlowDeck CI / docker (pull_request) Skipped
This commit is contained in:
@@ -0,0 +1,117 @@
|
|||||||
|
# Flowdeck — Connecteurs à référence ObsiGate (Phase 4, issue ObsiGate #22)
|
||||||
|
|
||||||
|
## Principe
|
||||||
|
|
||||||
|
Un connecteur personnel d'un workspace peut pointer vers une référence
|
||||||
|
ObsiGate au lieu d'un jeton :
|
||||||
|
|
||||||
|
```
|
||||||
|
coffre:<slug>/<nom> ex. coffre:flowdeck/gitea-lecture
|
||||||
|
```
|
||||||
|
|
||||||
|
* Le champ jeton du connecteur **devient** le champ référence : aucun
|
||||||
|
jeton n'est stocké pour ce connecteur (`secret_encrypted` reste vide).
|
||||||
|
* L'octroi est **toujours lié à un `workspace_id`** : créer un connecteur
|
||||||
|
à référence sans workspace est refusé (HTTP 400 / `ValueError`).
|
||||||
|
* À l'appel, le **serveur** Flowdeck appelle
|
||||||
|
`POST /api/coffres/distribuer` d'ObsiGate avec le jeton consommateur
|
||||||
|
Flowdeck et un contexte `flowdeck_workspace / <workspace_id>`, utilise
|
||||||
|
la valeur **en mémoire**, puis l'oublie. La valeur n'est jamais
|
||||||
|
persistée, jamais mise en cache, jamais journalisée, jamais renvoyée
|
||||||
|
à une réponse API ni au LLM.
|
||||||
|
|
||||||
|
## Configuration (serveur Flowdeck)
|
||||||
|
|
||||||
|
| Variable | Défaut | Rôle |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `OBSIGATE_COFFRE_URL` | dérivée de `DRACO_SSO_URL` | Base ObsiGate pour `/api/coffres/distribuer` |
|
||||||
|
| `OBSIGATE_CONSUMER_TOKEN` | (vide = distribution désactivée) | Jeton **consommateur** Flowdeck enregistré dans ObsiGate |
|
||||||
|
|
||||||
|
Le jeton consommateur est distinct du jeton MCP de notes et de la
|
||||||
|
session SSO. Il ne figure dans aucun log, rapport ou réponse.
|
||||||
|
|
||||||
|
## Isolation workspace — ce qui est garanti, et sa limite honnête
|
||||||
|
|
||||||
|
**Garanti côté Flowdeck, avant tout appel** (`connectors.resolve_secret`) :
|
||||||
|
|
||||||
|
* un connecteur à référence ne se résout que pour son propre
|
||||||
|
`workspace_id` ;
|
||||||
|
* workspace A tentant d'utiliser le connecteur (donc la référence /
|
||||||
|
l'octroi) du workspace B reçoit un refus explicite
|
||||||
|
(`CoffreWorkspaceMismatch` : « … ne peut pas utiliser ce connecteur
|
||||||
|
(il appartient au workspace …) : octroi d'un autre workspace refusé »)
|
||||||
|
et **le distributeur n'est pas appelé** ;
|
||||||
|
* un `workspace_id` déjà posé sur un connecteur est immuable : on ne
|
||||||
|
transfère pas un octroi en éditant le connecteur ;
|
||||||
|
* un connecteur à référence testé/appelé sans `workspace_id` échoue
|
||||||
|
proprement, jamais avec la valeur d'un autre workspace.
|
||||||
|
|
||||||
|
**Limite à connaître** : côté ObsiGate, l'octroi est accordé au
|
||||||
|
consommateur **Flowdeck** (un jeton par application), pas à un workspace
|
||||||
|
— le `contexte_id` transmis sert à la traçabilité, pas à un contrôle
|
||||||
|
d'accès côté ObsiGate. L'isolation par workspace repose donc sur le
|
||||||
|
contrôle Flowdeck ci-dessus. Une isolation appliquée côté ObsiGate
|
||||||
|
exigerait un consommateur par workspace : c'est un reste à faire
|
||||||
|
décidé côté ObsiGate (enregistrement de consommateurs par workspace),
|
||||||
|
pas un prérequis pour utiliser ce mécanisme.
|
||||||
|
|
||||||
|
## Échecs propres, sans repli
|
||||||
|
|
||||||
|
| Situation | Comportement Flowdeck |
|
||||||
|
| --- | --- |
|
||||||
|
| ObsiGate injoignable / timeout / HTTP 5xx | `CoffreIndisponible` — l'appel échoue, **aucune** valeur en cache n'est utilisée (il n'y en a pas) |
|
||||||
|
| Octroi révoqué / consommateur révoqué (403) | `CoffreAccesRefuse` — message « accès révoqué, à ré-accorder dans ObsiGate » |
|
||||||
|
| Octroi expiré (403) / secret expiré (410) | `CoffreAccesRefuse` avec le code ObsiGate, sans la valeur |
|
||||||
|
| Référence mal formée | `CoffreReferenceInvalide` (format `coffre:<slug>/<nom>`, slug 3–64, nom 3–128, minuscules, chiffres, `-`/`_`, aligné sur ObsiGate) |
|
||||||
|
|
||||||
|
## Secrets déjà chiffrés : pas de big-bang
|
||||||
|
|
||||||
|
Les connecteurs historiques (`secret_encrypted` Fernet) restent valides
|
||||||
|
tels quels. La migration est **opportuniste, connecteur par
|
||||||
|
connecteur** :
|
||||||
|
|
||||||
|
1. (optionnel) rattacher d'abord le connecteur à son workspace
|
||||||
|
(`PATCH /api/agent/connectors/{id}` avec `workspace_id`) ;
|
||||||
|
2. puis basculer vers la référence (`PATCH …` avec
|
||||||
|
`coffre_reference: "coffre:<slug>/<nom>"`) — le jeton local dormant
|
||||||
|
n'est pas effacé automatiquement, il est simplement ignoré tant que
|
||||||
|
la référence est posée ; effacer la référence (`""`) le réactive ;
|
||||||
|
3. vérifier par un probe avec `workspace_id`, puis supprimer le jeton
|
||||||
|
local si besoin (rotation côté ObsiGate).
|
||||||
|
|
||||||
|
## Chemin de migration par connecteur
|
||||||
|
|
||||||
|
| Connecteur actuel | État dans cette branche | Chemin |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Personnels (`custom`, `discord`, `telegram`, `mcp`, `tiktak`, `obsigate`) | **Implémenté** : `workspace_id` + `coffre_reference`, résolution en mémoire, isolation testée | Basculer connecteur par connecteur (ci-dessus) |
|
||||||
|
| Natifs Gitea / GitHub | **Inchangés** : jetons globaux d'instance (`GITEA_TOKEN`, `GITHUB_TOKEN` en `.env`) | Créer un connecteur personnel workspace-scopé à référence vers le même service et l'utiliser à la place du natif, quand le besoin workspace par workspace se présente ; les natifs ne sont pas supprimés (pas de big-bang) |
|
||||||
|
| LLM global (`llm_config` id=1) et clés par utilisateur (`user_llm_keys`) | **Inchangés** : clés en clair en base (globale / par utilisateur, pas par workspace) | Travail ultérieur dédié : ajouter une colonne référence sur `user_llm_keys` + résolution par workspace, avec le même patron (distribution en mémoire, workspace obligatoire) ; hors périmètre de cette branche |
|
||||||
|
| OAuth Google / M365 (`connector_tokens`) | **Inchangés** : jetons OAuth chiffrés par (kind, utilisateur) | Hors périmètre : le flux OAuth ne se prête pas à une référence coffre sans repenser le refresh ; à traiter séparément si besoin |
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
* `POST /api/agent/connectors` accepte `workspace_id` et
|
||||||
|
`coffre_reference` (référence sans workspace → 400).
|
||||||
|
* `PATCH /api/agent/connectors/{id}` accepte `workspace_id`
|
||||||
|
(premier rattachement seulement) et `coffre_reference` (`""` =
|
||||||
|
effacer la référence).
|
||||||
|
* `POST /api/agent/connectors/probe` accepte `workspace_id`.
|
||||||
|
* Réponses : `workspace_id`, `coffre_reference`, `secret_source`
|
||||||
|
(`coffre` | `chiffre_local` | `aucun`) — jamais de valeur.
|
||||||
|
* `GET /api/agent/connectors` liste les connecteurs avec ces champs.
|
||||||
|
|
||||||
|
## Ce qui reste à faire (validation réelle)
|
||||||
|
|
||||||
|
* Valider contre une vraie instance ObsiGate : créer le consommateur
|
||||||
|
Flowdeck + les octrois, puis un probe réel avec `workspace_id`
|
||||||
|
(les tests ici utilisent un faux endpoint de distribution).
|
||||||
|
* Threader `workspace_id` depuis l'agent : `AgentEngine.workspace_id`
|
||||||
|
→ `connector_fetch` (outil LLM) et `McpTool.call_tool` — aujourd'hui
|
||||||
|
ces chemins appellent sans workspace et échouent proprement pour les
|
||||||
|
connecteurs à référence (refus, pas de fuite) ; les connecteurs
|
||||||
|
historiques ne sont pas affectés.
|
||||||
|
* Le cas échéant, consommateur ObsiGate par workspace (voir limite
|
||||||
|
ci-dessus) et bascule des natifs Gitea/GitHub et des clés LLM
|
||||||
|
(tableau ci-dessus).
|
||||||
|
* Interface : sélecteur de référence dans le formulaire connecteur
|
||||||
|
(aujourd'hui, champ `coffre_reference` via l'API seulement).
|
||||||
Reference in New Issue
Block a user