HUB-19 (suite) — Propagation des rôles du Hub à ObsiGate (#228, cible 2.85.1)
Le pilote ShaarIt (0.7.0, en production) a posé le contrat du registre des rôles du Hub (plan Hub v2 §2.2, décision D3(a)). Cette PR le porte à ObsiGate, hôte à la fois émettrice et consommatrice du SSO. Aucune fusion demandée : branche + PR seulement.
Hypothèse de version (à lire avant fusion)
Branche créée depuis le main distant 2.84.1 (c8d2eb1), cible 2.85.1 via scripts/bump_version.py --set 2.85.1 — à rebaser après la PR #68 « API sessions HUB-22 » (2.85.0) si elle fusionne d'abord : la rotation du CHANGELOG et les fichiers dérivés de version se réconcilieront au rebase.
Réconciliation faite avant ouverture : le main distant a bougé pendant le chantier (commit HUB-22 direct 3876f0e puis son revert 5127510 — écart net nul vérifié par git diff). La branche est rebasée sur le main actuel (5127510) ; comparaison distante : 1 commit, 15 fichiers, tous voulus.
ID stable : la PR #68 (HUB-22) porte l'item #227 ; cette livraison prend donc l'ID #228, créé selon le processus du dépôt (AGENTS.md / DELIVERY_WORKFLOW) — aucun conflit d'ID dans les deux ordres de fusion.
Contrat consommé (D3(a))
À l'ouverture de session draco — chez ObsiGate : la validation locale du cookie partagé, draco_session.user_for_draco() (ACC-3.2, #217) — consultation de GET {hub}/api/roles/obsigate :
en-tête Cookie complet relayé tel quel, Accept: application/json, délai 2 s → 200 {"app", "sub", "role", "explicite", "ttl_secondes": 60}, rôle ∈ {aucun, utilisateur, administrateur} ;
URL du Hub : draco_config.app_url("hub"), la résolution de la barre commune (#223) — variable explicite DRACO_HUB_URL d'abord, dérivation par l'hôte sœur (suffixe d'environnement de l'hôte de l'agent) ensuite, défaut officiel enfin. Jamais d'URL dev figée.
Client — backend/hub_roles.py (nouveau)
Sur le patron du pilote (src/hub_roles.rs de ShaarIt) : cache mémoire par sub, TTL 60 s, horodatage posé avant l'appel (un échec conserve l'ancien cache, pas de rafale). Toute erreur (réseau, délai, statut ≠ 200, JSON illisible, rôle hors contrat) → rôle inconnu → comportement actuel, aucun verrouillage nouveau.
Effets (sessions établies par draco seulement)
Niveau connu aucun → user_for_draco refuse : pas de session établie par draco ; require_auth répond 403 avec un message explicite en français (refus consigné via request.state.role_hub_refuse). L'authentification native d'ObsiGate n'est jamais touchée — c'est l'émettrice, son accès propre est le secours.
Routes /api/auth/admin/* (CRUD des comptes) : nouvelle garde require_admin_hub dans backend/auth/middleware.py (= require_admin + canal draco et rôle connu ⇒ niveau administrateur du registre exigé). Sessions natives → règles actuelles ; rôle inconnu → règles actuelles. Le registre n'élève aucun privilège : un compte local non admin reste refusé par la règle locale.
Inchangés : validation locale du jeton, émission du cookie, révocations, renouvellement glissant. Aucun rôle dans le jeton.
Périmètre assumé : la garde du registre vise les routes nommées par la carte, /api/auth/admin/*. Les autres surfaces gardées par require_admin (configuration, vaults, coffres…) gardent leurs règles actuelles ; require_admin_hub est réutilisable tel quel si l'extension est décidée.
Fichiers (15)
backend/hub_roles.py (nouveau) — client du registre.
backend/auth/router.py — les 4 routes /admin/users* passent à require_admin_hub.
tests/test_hub_roles.py (nouveau) — 20 tests : parsing du contrat (URL, Cookie relayé, Accept, délai), rôles, erreurs → inconnu, résolution d'URL (explicite / dérivée -dev / défaut), cache par sub, TTL, échec sans rafale ; intégration draco (refus explicite, login natif intact sous « aucun », inconnu = actuel, utilisateur refusé en admin, administrateur accepté, non-admin local non élevé, session native jamais bloquée).
CHANGELOG.md (section [2.85.1]), docs/ROADMAP.md (#228), fiche docs/features/hub-roles-obsigate-228.md, dérivés de version synchronisés par le bump (VERSION, package.json, desktop, README FR/EN). Pas d'impact frontend → pas d'i18n.
Vérifications (environnement de revérification local, venv du dépôt)
pytest, cette branche : 42 échecs / 1917 réussis / 1 ignoré — listes d'échecs strictement identiques (fonds préexistant de l'environnement : test_agent_loop 17, test_ai_chat 10, test_collab 6, test_watcher 4, test_pdf 2, test_ai_e2e 2, test_perf_phase3 1 — aucune dans les zones touchées) ; les 20 nouveaux tests passent, ainsi que les suites voisines (test_draco_session, test_comptes_machine, test_draco_config, test_draco_barre, test_version).
ruffbackend/ : 0 erreur. mypybackend/ --ignore-missing-imports : Success: no issues found in 128 source files.
Note d'environnement : blocage d'écriture transitoire à l'échelle de la VM (~50 min) pendant le chantier ; la référence vierge avait été capturée avant, et la comparaison complète a été refaite après rétablissement, dans le même environnement.
## HUB-19 (suite) — Propagation des rôles du Hub à ObsiGate (#228, cible 2.85.1)
Le pilote ShaarIt (0.7.0, en production) a posé le contrat du registre des rôles du Hub (plan Hub v2 §2.2, décision D3(a)). Cette PR le porte à **ObsiGate**, hôte à la fois **émettrice et consommatrice** du SSO. Aucune fusion demandée : branche + PR seulement.
### Hypothèse de version (à lire avant fusion)
- Branche créée depuis le main distant **2.84.1** (`c8d2eb1`), cible **2.85.1** via `scripts/bump_version.py --set 2.85.1` — **à rebaser après la PR #68 « API sessions HUB-22 » (2.85.0) si elle fusionne d'abord** : la rotation du CHANGELOG et les fichiers dérivés de version se réconcilieront au rebase.
- **Réconciliation faite avant ouverture** : le main distant a bougé pendant le chantier (commit HUB-22 direct `3876f0e` puis son revert `5127510` — écart net **nul** vérifié par `git diff`). La branche est **rebasée sur le main actuel** (`5127510`) ; comparaison distante : 1 commit, 15 fichiers, tous voulus.
- **ID stable** : la PR #68 (HUB-22) porte l'item **#227** ; cette livraison prend donc l'ID **#228**, créé selon le processus du dépôt (AGENTS.md / DELIVERY_WORKFLOW) — aucun conflit d'ID dans les deux ordres de fusion.
### Contrat consommé (D3(a))
À l'ouverture de session draco — chez ObsiGate : la validation locale du cookie partagé, `draco_session.user_for_draco()` (ACC-3.2, #217) — consultation de `GET {hub}/api/roles/obsigate` :
- en-tête **Cookie complet** relayé tel quel, `Accept: application/json`, délai **2 s** → 200 `{"app", "sub", "role", "explicite", "ttl_secondes": 60}`, rôle ∈ {`aucun`, `utilisateur`, `administrateur`} ;
- **URL du Hub** : `draco_config.app_url("hub")`, la résolution de la barre commune (#223) — variable explicite `DRACO_HUB_URL` d'abord, dérivation par l'hôte sœur (suffixe d'environnement de l'hôte de l'agent) ensuite, défaut officiel enfin. Jamais d'URL dev figée.
### Client — `backend/hub_roles.py` (nouveau)
Sur le patron du pilote (`src/hub_roles.rs` de ShaarIt) : cache mémoire par `sub`, TTL 60 s, **horodatage posé avant l'appel** (un échec conserve l'ancien cache, pas de rafale). Toute erreur (réseau, délai, statut ≠ 200, JSON illisible, rôle hors contrat) → rôle **inconnu** → comportement actuel, aucun verrouillage nouveau.
### Effets (sessions établies par draco seulement)
1. **Niveau connu `aucun`** → `user_for_draco` refuse : pas de session établie par draco ; `require_auth` répond **403** avec un message explicite en français (refus consigné via `request.state.role_hub_refuse`). **L'authentification native d'ObsiGate n'est jamais touchée** — c'est l'émettrice, son accès propre est le secours.
2. **Routes `/api/auth/admin/*`** (CRUD des comptes) : nouvelle garde `require_admin_hub` dans `backend/auth/middleware.py` (= `require_admin` + canal draco et rôle connu ⇒ niveau `administrateur` du registre exigé). Sessions natives → règles actuelles ; rôle inconnu → règles actuelles. Le registre n'élève aucun privilège : un compte local non admin reste refusé par la règle locale.
3. **Inchangés** : validation locale du jeton, émission du cookie, révocations, renouvellement glissant. **Aucun rôle dans le jeton.**
**Périmètre assumé** : la garde du registre vise les routes nommées par la carte, `/api/auth/admin/*`. Les autres surfaces gardées par `require_admin` (configuration, vaults, coffres…) gardent leurs règles actuelles ; `require_admin_hub` est réutilisable tel quel si l'extension est décidée.
### Fichiers (15)
- `backend/hub_roles.py` (nouveau) — client du registre.
- `backend/draco_session.py` — consultation dans `user_for_draco`, `_hub_role` joint, refus « aucun » consigné.
- `backend/auth/middleware.py` — réponse explicite dans `require_auth` (nouveau paramètre `request`), garde `require_admin_hub`.
- `backend/auth/router.py` — les 4 routes `/admin/users*` passent à `require_admin_hub`.
- `tests/test_hub_roles.py` (nouveau) — 20 tests : parsing du contrat (URL, Cookie relayé, `Accept`, délai), rôles, erreurs → inconnu, résolution d'URL (explicite / dérivée `-dev` / défaut), cache par `sub`, TTL, échec sans rafale ; intégration draco (refus explicite, login natif intact sous « aucun », inconnu = actuel, `utilisateur` refusé en admin, `administrateur` accepté, non-admin local non élevé, session native jamais bloquée).
- `CHANGELOG.md` (section `[2.85.1]`), `docs/ROADMAP.md` (#228), fiche `docs/features/hub-roles-obsigate-228.md`, dérivés de version synchronisés par le bump (VERSION, package.json, desktop, README FR/EN). Pas d'impact frontend → pas d'i18n.
### Vérifications (environnement de revérification local, venv du dépôt)
- **pytest, arbre vierge (main 2.84.1)** : 42 échecs / 1897 réussis / 1 ignoré.
- **pytest, cette branche** : 42 échecs / **1917 réussis** / 1 ignoré — **listes d'échecs strictement identiques** (fonds préexistant de l'environnement : `test_agent_loop` 17, `test_ai_chat` 10, `test_collab` 6, `test_watcher` 4, `test_pdf` 2, `test_ai_e2e` 2, `test_perf_phase3` 1 — aucune dans les zones touchées) ; les 20 nouveaux tests passent, ainsi que les suites voisines (`test_draco_session`, `test_comptes_machine`, `test_draco_config`, `test_draco_barre`, `test_version`).
- **ruff** `backend/` : 0 erreur. **mypy** `backend/ --ignore-missing-imports` : `Success: no issues found in 128 source files`.
- Note d'environnement : blocage d'écriture transitoire à l'échelle de la VM (~50 min) pendant le chantier ; la référence vierge avait été capturée avant, et la comparaison complète a été refaite après rétablissement, dans le même environnement.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
HUB-19 (suite) — Propagation des rôles du Hub à ObsiGate (#228, cible 2.85.1)
Le pilote ShaarIt (0.7.0, en production) a posé le contrat du registre des rôles du Hub (plan Hub v2 §2.2, décision D3(a)). Cette PR le porte à ObsiGate, hôte à la fois émettrice et consommatrice du SSO. Aucune fusion demandée : branche + PR seulement.
Hypothèse de version (à lire avant fusion)
c8d2eb1), cible 2.85.1 viascripts/bump_version.py --set 2.85.1— à rebaser après la PR #68 « API sessions HUB-22 » (2.85.0) si elle fusionne d'abord : la rotation du CHANGELOG et les fichiers dérivés de version se réconcilieront au rebase.3876f0epuis son revert5127510— écart net nul vérifié pargit diff). La branche est rebasée sur le main actuel (5127510) ; comparaison distante : 1 commit, 15 fichiers, tous voulus.Contrat consommé (D3(a))
À l'ouverture de session draco — chez ObsiGate : la validation locale du cookie partagé,
draco_session.user_for_draco()(ACC-3.2, #217) — consultation deGET {hub}/api/roles/obsigate:Accept: application/json, délai 2 s → 200{"app", "sub", "role", "explicite", "ttl_secondes": 60}, rôle ∈ {aucun,utilisateur,administrateur} ;draco_config.app_url("hub"), la résolution de la barre commune (#223) — variable expliciteDRACO_HUB_URLd'abord, dérivation par l'hôte sœur (suffixe d'environnement de l'hôte de l'agent) ensuite, défaut officiel enfin. Jamais d'URL dev figée.Client —
backend/hub_roles.py(nouveau)Sur le patron du pilote (
src/hub_roles.rsde ShaarIt) : cache mémoire parsub, TTL 60 s, horodatage posé avant l'appel (un échec conserve l'ancien cache, pas de rafale). Toute erreur (réseau, délai, statut ≠ 200, JSON illisible, rôle hors contrat) → rôle inconnu → comportement actuel, aucun verrouillage nouveau.Effets (sessions établies par draco seulement)
aucun→user_for_dracorefuse : pas de session établie par draco ;require_authrépond 403 avec un message explicite en français (refus consigné viarequest.state.role_hub_refuse). L'authentification native d'ObsiGate n'est jamais touchée — c'est l'émettrice, son accès propre est le secours./api/auth/admin/*(CRUD des comptes) : nouvelle garderequire_admin_hubdansbackend/auth/middleware.py(=require_admin+ canal draco et rôle connu ⇒ niveauadministrateurdu registre exigé). Sessions natives → règles actuelles ; rôle inconnu → règles actuelles. Le registre n'élève aucun privilège : un compte local non admin reste refusé par la règle locale.Périmètre assumé : la garde du registre vise les routes nommées par la carte,
/api/auth/admin/*. Les autres surfaces gardées parrequire_admin(configuration, vaults, coffres…) gardent leurs règles actuelles ;require_admin_hubest réutilisable tel quel si l'extension est décidée.Fichiers (15)
backend/hub_roles.py(nouveau) — client du registre.backend/draco_session.py— consultation dansuser_for_draco,_hub_rolejoint, refus « aucun » consigné.backend/auth/middleware.py— réponse explicite dansrequire_auth(nouveau paramètrerequest), garderequire_admin_hub.backend/auth/router.py— les 4 routes/admin/users*passent àrequire_admin_hub.tests/test_hub_roles.py(nouveau) — 20 tests : parsing du contrat (URL, Cookie relayé,Accept, délai), rôles, erreurs → inconnu, résolution d'URL (explicite / dérivée-dev/ défaut), cache parsub, TTL, échec sans rafale ; intégration draco (refus explicite, login natif intact sous « aucun », inconnu = actuel,utilisateurrefusé en admin,administrateuraccepté, non-admin local non élevé, session native jamais bloquée).CHANGELOG.md(section[2.85.1]),docs/ROADMAP.md(#228), fichedocs/features/hub-roles-obsigate-228.md, dérivés de version synchronisés par le bump (VERSION, package.json, desktop, README FR/EN). Pas d'impact frontend → pas d'i18n.Vérifications (environnement de revérification local, venv du dépôt)
test_agent_loop17,test_ai_chat10,test_collab6,test_watcher4,test_pdf2,test_ai_e2e2,test_perf_phase31 — aucune dans les zones touchées) ; les 20 nouveaux tests passent, ainsi que les suites voisines (test_draco_session,test_comptes_machine,test_draco_config,test_draco_barre,test_version).backend/: 0 erreur. mypybackend/ --ignore-missing-imports:Success: no issues found in 128 source files.c1d69af69dtodcf847e2fd