feat: HUB-21 — réglages généraux du Hub consommés par ObsiGate (bannière + drapeau recherche_partout) #229
CI / lint (pull_request) Successful in 1m16s
CI / security (pull_request) Successful in 19s
CI / test (pull_request) Successful in 3m41s
CI / build (pull_request) Successful in 13s
CI / e2e (pull_request) Failing after 14m58s

This commit is contained in:
Spark
2026-11-06 17:02:04 +00:00
parent 15d015e59b
commit b88191ffcb
17 changed files with 647 additions and 15 deletions
@@ -0,0 +1,82 @@
# #229 — HUB-21 : consommation des réglages généraux du Hub par ObsiGate
**Carte :** propagation de HUB-21 (fusion #135) — le pilote TikTak
(0.61.22, en production) a posé le patron de client ; ObsiGate le
consomme à son tour, comme les autres hôtes de la suite.
**Version cible :** 2.85.2 (branche depuis le main 2.85.1).
## Contrat consommé (liste fermée D6(a))
- `GET {hub}/api/reglages/effectifs` — endpoint **public** (aucun
cookie relayé), `Accept: application/json`, délai **2 s** ;
- réponse 200, forme figée : `{"recherche_partout": bool,
"banniere": str, "domaine_defaut": "pro"|"famille"|"perso"|null,
"ttl_secondes": 60}`, `Cache-Control: public, max-age=60` ;
- **URL du Hub** : `draco_config.app_url("hub")`, la résolution de la
barre commune (#223) — variable explicite d'abord, dérivation par
l'hôte sœur ensuite, défaut officiel enfin. Jamais d'URL dev figée.
## Client — `backend/hub_reglages.py`
Sur le patron du pilote TikTak (`app/hub_reglages.py`, httpx → urllib
comme `backend/hub_roles.py`) :
- cache mémoire **60 s** ; horodatage posé **avant** l'appel : un
échec conserve l'ancien cache et ne déclenche pas de rafale vers un
Hub en panne ;
- forme validée strictement ; toute erreur (réseau, délai, statut
≠ 200, JSON illisible, forme hors contrat) → **repli intégral** sur
les défauts actuels (`recherche_partout: true`, `banniere: ""`,
`domaine_defaut: null`) — le comportement d'ObsiGate avant HUB-21.
Le module ne lève jamais ; aucun appel au démarrage.
## Consommation
1. **`banniere`** — `backend/main.py::_fragment_banniere_hub()`
construit le fragment (texte **échappé** via `html.escape`, jamais
de HTML injecté), injecté sous la barre commune par `_index_html`
(page principale, juste avant le rappel de la barre) et par
`serve_recherche_page` (page /recherche, après l'en-tête de la
barre). Le script inline (nonce CSP ajouté par `inject_csp_nonce`)
révèle le bandeau sauf si le même texte a été fermé dans la
session du navigateur (`sessionStorage`, clé
`obsigate_banniere_masquee`) : une nouvelle annonce réapparaît.
Bannière vide = aucun fragment rendu, zéro changement visuel.
Styles `.banniere-hub*` dans `frontend/style.css`, sur les
variables du thème (`var(--accent)`).
2. **`recherche_partout`** — `serve_recherche_page` remplace le jeton
`{{RECHERCHE_PARTOUT}}` de `frontend/recherche.html`, rendu en
attribut `data-recherche-partout` du `<main id="recherche">`. Le
JS vendangé `frontend/js/recherche.js` (modification identique
dans les 4 hôtes applicatifs : fonction pure
`federationAutorisee`) n'interroge plus les applications sœurs ni
la console quand la valeur est `false` — leurs sections affichent
« Recherche fédérée désactivée… » — tandis que la recherche locale
ObsiGate (même origine) continue. Attribut absent ou illisible =
fédération active (repli).
3. **`domaine_defaut` — non consommé.** ObsiGate n'a pas de notion
de domaine (Pro/Famille/Perso) propre à ses vues : le sélecteur de
domaine de la barre commune pilote le panneau Spark, pas
ObsiGate. La valeur est lue et validée par le client, sans effet
local. Seul l'hôte console (agent) applique ce réglage.
## i18n
Aucun texte d'interface du SPA n'est ajouté : la bannière est un
contenu d'annonce servi par le Hub (texte libre de l'administrateur),
et le libellé « Fermer l'annonce » est l'attribut d'accessibilité du
fragment injecté côté serveur, sur le même patron que les autres
hôtes. Pas de clé `frontend/locales/*` ajoutée.
## Tests
- `tests/test_hub_reglages.py` (nouveau) : contrat exact de l'appel
(URL, aucun cookie, délai 2 s), formes inattendues → repli, erreur
HTTP / Hub injoignable / JSON illisible → repli, cache 60 s,
ancien cache conservé sans rafale ; intégration TestClient :
bannière présente et échappée sur `/` et `/recherche`, absente si
vide ou Hub muet, `data-recherche-partout` selon la valeur du
drapeau (faux / vrai / Hub muet).
- `tests/frontend/recherche.test.mjs` : assertions ajoutées pour
`federationAutorisee` (seules les valeurs explicitement négatives
désactivent ; absent/illisible = actif).