ObsiGate 2.85.1 — HUB-19 : propagation des rôles du Hub (consommatrice, registre D3(a)) (#228) #69

Merged
bruno merged 1 commits from hub19-roles-obsigate-2-85-1 into main 2026-11-06 11:31:26 -05:00
Owner

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.
## 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.
bruno added 1 commit 2026-11-06 11:31:05 -05:00
feat: HUB-19 — rôles du Hub consommés par ObsiGate (registre D3(a)) #228
CI / lint (pull_request) Successful in 1m17s
CI / security (pull_request) Successful in 21s
CI / test (pull_request) Failing after 3m39s
CI / build (pull_request) Skipped
CI / e2e (pull_request) Skipped
dcf847e2fd
bruno force-pushed hub19-roles-obsigate-2-85-1 from c1d69af69d to dcf847e2fd 2026-11-06 11:31:05 -05:00 Compare
bruno merged commit 15d015e59b into main 2026-11-06 11:31:26 -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/ObsiGate#69