Kanbante 0.46.0 — HUB-16 : correspondance draco par utilisateur (JIT + liaison admin) #84

Merged
bruno merged 6 commits from hub16-correspondance-draco into main 2026-11-06 09:45:10 -05:00
Owner

Carte : fusion #130 — HUB-16 — Kanbante : correspondance par utilisateur (plan Hub v2.1, §2.2 mécanisme 3 et §4 ; décision D5 de Bruno). Version visée : 0.46.0 (mineur : c'est une fonction — patron du dépôt : fonctions en mineur comme 0.24.0/0.25.0, les 0.45.x étaient vendanges et correctifs). Base revérifiée : production et main distant en 0.45.12 (37aa83ee, aucune collision de numéro).

Le problème

Le pont SSO draco_session (ACC-3.2) était mono-utilisateur par hypothèse : sans correspondance, un jeton draco valide ouvrait la session du compte unique — ou, sans aucun compte, celle de l'opérateur d'environnement — et /api/search acceptait tout jeton valide « en tant qu'opérateur ». Le critère « un compte créé fonctionne dans toutes les applications » ne pouvait pas tenir dès le deuxième utilisateur.

Modèle de correspondance retenu

La correspondance vit dans le modèle existant : la colonne users.draco_sub (texte, UNIQUE, présente depuis la 0.24.0) porte le sub draco de chaque compte ; models.get_user_by_draco(sub) était déjà la résolution canonique. Aucune nouvelle table : le patron était déjà là, il manquait la décision et le provisionnement. La règle complète est centralisée dans app.accounts.resolve_draco_user(), seul point d'entrée du pont /auth/draco et du contrôle draco de /api/search :

  1. session native ouverte + sub libre → liaison à ce compte (première visite doublement connectée, inchangé) ;
  2. sub lié → session de son compte (refus si désactivé, sans contournement) ;
  3. amorçage (ci-dessous) ;
  4. sinon création JIT.

JIT : nom d'utilisateur = slug du nom affiché des claims, suffixé en cas de collision (jamais d'adoption d'un compte non lié, même homonyme — le JIT crée toujours un compte nouveau) ; rôle membre (utilisateur standard, jamais admin) ; mot de passe local = secret aléatoire haché, jamais divulgué (l'accès normal passe par draco ; un admin peut poser un mot de passe local via « Réinitialiser ») ; membre de l'espace Principal ; journalisé draco.jit.

Liaison par l'administrateur (comptes préexistants) : colonne « Draco (sub) » de l'écran d'administration existant /admin — saisir le sub, « Lier » ; « Délier » pour retirer ; refus si le sub est déjà lié à un autre compte ; journalisé draco.link.admin / draco.unlink.admin. Choix du patron existant plutôt qu'un nouvel écran : /admin gérait déjà les utilisateurs (rôle, activation, réinitialisation) et affichait déjà l'état de liaison — c'est son prolongement naturel, un seul endroit pour administrer les comptes. Procédure et modèle documentés dans docs/correspondance-draco.md. Le registre matriciel côté Hub viendra avec HUB-19 : il lira cette correspondance, dont Kanbante reste la source de vérité locale.

Traitement du compte d'amorçage existant

S'il n'existe qu'un seul compte, que c'est un admin actif sans liaison, le premier sub valide s'y lie au premier passage (journalisé draco.link.amorcage) et ouvre sa session : le compte actuel de l'opérateur (créé depuis l'environnement) garde son accès, sans verrouillage. Restriction délibérée par rapport à l'ancien code, qui liait le compte unique quel que soit son rôle : un unique compte membre n'est plus « adopté » par la première identité draco venue — elle reçoit son propre compte JIT. Voies de secours inchangées dans tous les cas : mot de passe du compte, repli environnement (formulaire et Basic), et liaison manuelle par l'administrateur sur son propre compte (permise explicitement, contrairement au rôle/à l'activation).

Changement corrélé : sans aucun compte en base, un jeton draco n'ouvre plus la session de l'opérateur d'environnement — il crée un compte membre JIT (en pratique ce cas n'existe pas : l'amorçage crée l'admin au démarrage dès que l'authentification est configurée).

/api/search

Le cookie draco n'y vaut plus « opérateur » : le sub doit désigner un compte (correspondance ou amorçage lié au passage). Aucun JIT sur cette surface — la création a lieu à la première vraie connexion, par le pont.

Non-régressions vérifiées

  • Jetons personnels kbt_ : création, usage /mcp et /api/, révocation, refus sur l'interface web — inchangés (tests existants + un test ciblé sous authentification active).
  • Sessions propres Kanbante et connexion native, liaison à la première visite doublement connectée incluse : inchangées.
  • Basic (environnement) : inchangé. Pont inerte sans DRACO_SESSION_SECRET : inchangé (tests existants).
  • Compte désactivé : refusé par le pont comme par les jetons ; la correspondance existante interdit tout contournement JIT.
  • Réconciliation Kanbante/Gitea par étiquettes : non touchée.

Tests

  • Nouveaux : tests/test_draco_correspondance.py — 8 tests : amorçage unique admin ; deux identités → deux comptes distincts (+ retour d'une identité sur le même compte) ; JIT sans adoption d'un homonyme non lié ; compte désactivé refusé sans contournement ; liaison/déliaison admin + conflit de sub refusé ; liaison refusée aux non-admins ; /api/search par utilisateur ; jeton kbt_ toujours accepté.
  • Comparaison branche vs main vierge, même environnement, même interpréteur (venv neuf, proxies de la VM retirés — no_proxy casse httpx) : main vierge 387 réussis / 1 échec ; branche 395 réussis / 1 échec. L'écart est exactement les 8 nouveaux tests. L'échec unique est identique des deux côtés et préexistant : tests/test_mobile.py::test_menu_compte_draco_fonctionnel (il attend la branche profil du menu compte, retirée en 0.45.10 par la barre v0.3.0 — hors périmètre de cette carte).
  • python -m py_compile app/*.py : OK (étape CI).

Reste à faire (hors de cette PR)

  • Fusion sur l'ordre de Bruno, puis release v0.46.0 et redéploiement (règle dev : la publication Gitea déploie).
  • Mise à jour de la section d'aide du Hub (guide Kanbante) pour la liaison administrateur — chantier Hub séparé, à grouper avec les autres cartes de la vague B.

🤖 Carte fusion #130 — aucune fusion effectuée par l'agent, la carte avance en Révision seulement.

**Carte : fusion #130 — HUB-16 — Kanbante : correspondance par utilisateur** (plan Hub v2.1, §2.2 mécanisme 3 et §4 ; décision D5 de Bruno). Version visée : **0.46.0** (mineur : c'est une fonction — patron du dépôt : fonctions en mineur comme 0.24.0/0.25.0, les 0.45.x étaient vendanges et correctifs). Base revérifiée : production et `main` distant en **0.45.12** (`37aa83ee`, aucune collision de numéro). ## Le problème Le pont SSO `draco_session` (ACC-3.2) était mono-utilisateur par hypothèse : sans correspondance, un jeton draco valide ouvrait la session du compte unique — ou, sans aucun compte, celle de l'opérateur d'environnement — et `/api/search` acceptait tout jeton valide « en tant qu'opérateur ». Le critère « un compte créé fonctionne dans toutes les applications » ne pouvait pas tenir dès le deuxième utilisateur. ## Modèle de correspondance retenu La correspondance vit **dans le modèle existant** : la colonne **`users.draco_sub`** (texte, `UNIQUE`, présente depuis la 0.24.0) porte le `sub` draco de chaque compte ; `models.get_user_by_draco(sub)` était déjà la résolution canonique. Aucune nouvelle table : le patron était déjà là, il manquait la décision et le provisionnement. La règle complète est centralisée dans **`app.accounts.resolve_draco_user()`**, seul point d'entrée du pont `/auth/draco` et du contrôle draco de `/api/search` : 1. session native ouverte + `sub` libre → liaison à ce compte (première visite doublement connectée, **inchangé**) ; 2. `sub` lié → session de **son** compte (refus si désactivé, sans contournement) ; 3. amorçage (ci-dessous) ; 4. sinon **création JIT**. **JIT** : nom d'utilisateur = slug du nom affiché des claims, suffixé en cas de collision (**jamais d'adoption** d'un compte non lié, même homonyme — le JIT crée toujours un compte nouveau) ; rôle **membre** (utilisateur standard, jamais admin) ; mot de passe local = secret aléatoire haché, **jamais divulgué** (l'accès normal passe par draco ; un admin peut poser un mot de passe local via « Réinitialiser ») ; membre de l'espace Principal ; journalisé `draco.jit`. **Liaison par l'administrateur** (comptes préexistants) : colonne **« Draco (sub) » de l'écran d'administration existant `/admin`** — saisir le `sub`, « Lier » ; « Délier » pour retirer ; refus si le `sub` est déjà lié à un autre compte ; journalisé `draco.link.admin` / `draco.unlink.admin`. Choix du patron existant plutôt qu'un nouvel écran : `/admin` gérait déjà les utilisateurs (rôle, activation, réinitialisation) et affichait déjà l'état de liaison — c'est son prolongement naturel, un seul endroit pour administrer les comptes. Procédure et modèle documentés dans **`docs/correspondance-draco.md`**. Le registre matriciel côté Hub viendra avec HUB-19 : il lira cette correspondance, dont Kanbante reste la source de vérité locale. ## Traitement du compte d'amorçage existant S'il n'existe qu'**un seul compte**, que c'est un **admin actif sans liaison**, le premier `sub` valide s'y lie au premier passage (journalisé `draco.link.amorcage`) et ouvre sa session : le compte actuel de l'opérateur (créé depuis l'environnement) **garde son accès, sans verrouillage**. Restriction délibérée par rapport à l'ancien code, qui liait le compte unique **quel que soit son rôle** : un unique compte *membre* n'est plus « adopté » par la première identité draco venue — elle reçoit son propre compte JIT. Voies de secours inchangées dans tous les cas : mot de passe du compte, repli environnement (formulaire et Basic), et liaison manuelle par l'administrateur sur son propre compte (permise explicitement, contrairement au rôle/à l'activation). Changement corrélé : sans aucun compte en base, un jeton draco n'ouvre plus la session de l'opérateur d'environnement — il crée un compte membre JIT (en pratique ce cas n'existe pas : l'amorçage crée l'admin au démarrage dès que l'authentification est configurée). ## /api/search Le cookie draco n'y vaut plus « opérateur » : le `sub` doit désigner un compte (correspondance ou amorçage lié au passage). **Aucun JIT** sur cette surface — la création a lieu à la première vraie connexion, par le pont. ## Non-régressions vérifiées - Jetons personnels **`kbt_`** : création, usage `/mcp` et `/api/`, révocation, refus sur l'interface web — inchangés (tests existants + un test ciblé sous authentification active). - **Sessions propres Kanbante** et connexion native, liaison à la première visite doublement connectée incluse : inchangées. - **Basic** (environnement) : inchangé. Pont **inerte sans `DRACO_SESSION_SECRET`** : inchangé (tests existants). - Compte **désactivé** : refusé par le pont comme par les jetons ; la correspondance existante interdit tout contournement JIT. - Réconciliation Kanbante/Gitea par étiquettes : **non touchée**. ## Tests - Nouveaux : `tests/test_draco_correspondance.py` — **8 tests** : amorçage unique admin ; deux identités → deux comptes distincts (+ retour d'une identité sur le même compte) ; JIT sans adoption d'un homonyme non lié ; compte désactivé refusé sans contournement ; liaison/déliaison admin + conflit de `sub` refusé ; liaison refusée aux non-admins ; `/api/search` par utilisateur ; jeton `kbt_` toujours accepté. - Comparaison **branche vs main vierge, même environnement, même interpréteur** (venv neuf, proxies de la VM retirés — `no_proxy` casse httpx) : main vierge **387 réussis / 1 échec** ; branche **395 réussis / 1 échec**. L'écart est exactement les 8 nouveaux tests. L'échec unique est identique des deux côtés et préexistant : `tests/test_mobile.py::test_menu_compte_draco_fonctionnel` (il attend la branche `profil` du menu compte, retirée en 0.45.10 par la barre v0.3.0 — hors périmètre de cette carte). - `python -m py_compile app/*.py` : OK (étape CI). ## Reste à faire (hors de cette PR) - Fusion sur l'ordre de Bruno, puis release `v0.46.0` et redéploiement (règle dev : la publication Gitea déploie). - Mise à jour de la section d'aide du Hub (guide Kanbante) pour la liaison administrateur — chantier Hub séparé, à grouper avec les autres cartes de la vague B. 🤖 Carte fusion #130 — aucune fusion effectuée par l'agent, la carte avance en Révision seulement.
bruno added 6 commits 2026-11-06 08:54:42 -05:00
bruno merged commit 7c457db59f into main 2026-11-06 09:45:10 -05:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Projets/kanbante#84