Déployer et superviser

Observabilité de Lucarne

En bref

  • Audience : Matomo auto-hébergé, sans cookie, paramétré pour l'exemption de consentement CNIL.
  • Exploitation : Prometheus + Grafana + Blackbox Exporter, avec 3 tableaux et 10 alertes versionnés dans le dépôt.
  • Logs : JSON sur stdout, prêts pour Loki.
  • Le modèle est celui de La Suite numérique : un kit d'exploitation livré avec l'application, pas des tableaux construits à la main.
  • Aucune donnée personnelle ne sort vers un tiers. /api/metrics est fermé par jeton, contrairement au /v1/metrics public relevé par l'audit de l'existant.

Décision de principe : ADR 006. Exploitation courante : runbook.


1. Qui mesure quoi

BesoinOutilOù c'est branché
Visites, pages vues, provenance, appareils, termes de rechercheMatomo (auto-hébergé)src/features/observability/MatomoTracker.tsx
Recherches, recherches sans résultat, sorties vers HAL/ORCID/DOIPrometheus (compteurs agrégés)src/server/data/withSearchMetrics.ts, /api/telemetry
Fiches publiées, fraîcheur de la synchronisation, rejets de contratPrometheussrc/server/observability/collectors.ts, fournisseur amont
Santé de l'API amont (CRISalid) : statuts, latencePrometheussrc/server/data/providers/, appels amont
CPU, mémoire, boucle d'événements, redémarragesPrometheus (métriques Node par défaut)prom-client
Disponibilité vue de l'extérieurBlackbox Exporterops/observability/prometheus/prometheus.yml, k8s/probe.yaml
Tableaux de bordGrafanaops/observability/grafana/dashboards/*.json
AlertesPrometheus / Alertmanagerops/observability/prometheus/alerts.yml
Erreurs serveur détailléesLogs JSON + compteur ; GlitchTip en optionsrc/instrumentation.ts
Rapports trimestrielsExport Matomo + export Grafana§ 9

Ce qui est exclu :

  • Mixpanel et Sentry Session Replay : constats de l'audit de l'existant, et ADR 006.
  • PostHog : trop lourd pour une vitrine sans parcours produit complexe.
  • Metabase : utile plus tard, si Paris-Saclay veut des rapports BI croisant plusieurs bases.

Le modèle La Suite numérique

Les dépôts publics de La Suite numérique (DINUM) montrent le même découpage :

  • Matomo est configuré selon les recommandations de la CNIL (st-home _app.tsx).
  • Prometheus lit /metrics, qui expose des métriques HTTP, SQL et métier (docs/metrics.md).
  • Grafana affiche des tableaux versionnés en JSON dans Git (exemples).
  • cAdvisor et Grafana Alloy remontent métriques et logs (architecture).
  • Quelques métriques métier complètent l'ensemble (exemple Drive).

Lucarne reprend ce modèle à l'échelle d'une vitrine.

Qui mesure quoi : les flux de mesure autour de la vitrine

2. Ce qui est livré dans le dépôt

Code
src/ instrumentation.ts démarrage (build_info) + onRequestError (erreurs serveur) server/observability/ metrics.ts registre Prometheus et métriques de l'application collectors.ts métriques métier calculées au scrape (cache 1–5 min) access.ts contrôle d'accès de /api/metrics logger.ts logs JSON, clés sensibles masquées server/data/withSearchMetrics.ts toute liste avec `query` = une recherche mesurée server/data/providers/ fournisseur amont : appels mesurés et journalisés app/api/metrics/route.ts exposition Prometheus app/api/telemetry/route.ts réception des clics sortants (catégorie seule) app/api/health/route.ts sonde de vie (version, commit) features/observability/ Matomo + suivi des liens sortants, montés dans le layout utils/telemetry.ts classement des liens sortants, nettoyage des termes ops/observability/ docker-compose.yml pile locale : Prometheus, Grafana, Blackbox (+ Matomo) prometheus/prometheus.yml, alerts.yml scrape, sondes et règles d'alerte (source unique) blackbox/blackbox.yml module http_2xx grafana/build-dashboards.mjs générateur des tableaux (source de vérité) grafana/dashboards/*.json tableaux générés : santé, qualité éditoriale, usage grafana/provisioning/ datasource et chargement automatique des tableaux kustomization.yaml + k8s/ ServiceMonitor, Probe, PrometheusRule, ConfigMap des tableaux tests/unit/observability/ accès, métriques, télémétrie, cohérence tableaux/alertes

Commandes :

  • pnpm ops:k8s : régénère les tableaux JSON et k8s/prometheusrule.yaml après une modification du générateur ou de alerts.yml.
  • pnpm test : vérifie, entre autres, que chaque métrique citée dans un tableau ou une alerte est bien exposée par l'application.

Une seule dépendance a été ajoutée : prom-client (15.1.3), la bibliothèque Prometheus de référence pour Node.js. Elle respecte la politique des dépendances. Le logger est écrit à la main pour éviter une dépendance de plus.


3. Variables d'environnement

VariableCôtéDéfautRôle
METRICS_TOKENserveurvideJeton Bearer exigé par /api/metrics. À mettre dans un Secret, avec la même valeur côté Prometheus.
METRICS_ALLOW_UNAUTHENTICATEDserveurfalsetrue ouvre /api/metrics sans jeton en production. N'utiliser que si l'ingress ne route pas ce chemin.
LOG_LEVELserveurinfodebug, info, warn ou error.
APP_VERSION, GIT_COMMIT_SHAserveurpackage.json, unknownAffichés dans build_info et /api/health. À injecter par la CI au build de l'image.
NEXT_PUBLIC_ANALYTICS_ENABLEDbuildfalseActive Matomo, à condition que les deux variables suivantes soient renseignées.
NEXT_PUBLIC_MATOMO_URLbuildvideURL de l'instance Matomo, ex. https://stats.universite-paris-saclay.fr/.
NEXT_PUBLIC_MATOMO_SITE_IDbuildvideIdentifiant du site dans Matomo.
NEXT_PUBLIC_MATOMO_SEARCH_KEYWORDSbuildsanitizedsanitized : termes nettoyés. none : termes masqués, seuls le nombre de recherches et de résultats remonte.
OTEL_EXPORTER_OTLP_ENDPOINT, SENTRY_DSNserveurvideRéservées, non branchées (§ 7).

Les variables NEXT_PUBLIC_* sont inlinées au build. Une image Docker est donc construite pour un environnement donné : recette et production ont deux sites Matomo, donc deux images ou deux builds.

Comportement de /api/metrics :

SituationRéponse
METRICS_TOKEN défini, bon jeton200, format texte Prometheus
METRICS_TOKEN défini, jeton absent ou faux401
Pas de jeton, NODE_ENV=production404, sauf si METRICS_ALLOW_UNAUTHENTICATED=true
Pas de jeton, développement200

En production, bloquer aussi /api/metrics à l'ingress pour le trafic venant d'Internet. Le jeton est une deuxième barrière, pas la seule.


4. Métriques exposées

Toutes les métriques portent le préfixe saclay_research_. Les métriques Node par défaut aussi (saclay_research_process_cpu_seconds_total, saclay_research_nodejs_eventloop_lag_p99_seconds, etc.).

MétriqueTypeLabelsSignification
build_infogaugeversion, commit, data_sourceVersion déployée ; data_source vaut fixtures ou api.
searches_totalcounterentity_type, outcomeRecherches. outcome : results, zero_results ou error.
search_duration_secondshistogramentity_typeDurée des recherches côté serveur.
outbound_clicks_totalcountertargetClics sortants vers hal, orcid, doi, idref, scanr, openalex, ror, theses, paris_saclay ou other.
upstream_requests_totalcounteroperation, status_classAppels à l'API amont. status_class : 2xx, 4xx, 5xx ou network_error.
upstream_request_duration_secondshistogramoperationLatence de l'API amont.
contract_violations_totalcounteroperationRéponses amont rejetées par les schémas Zod, donc non publiées.
revalidations_totalcounteroutcomeAppels à /api/revalidate : success, unauthorized, bad_request ou error.
request_errors_totalcounterroute_type, route_pathErreurs serveur capturées par Next.js.
published_recordsgaugeentity_typeFiches servies par le fournisseur (center, researcher, project, laboratory).
last_successful_sync_timestamp_secondsgaugesourceFin de la dernière synchronisation réussie, lue dans la table sync_runs.
metric_collector_errors_totalcountercollectorÉchecs de calcul des deux gauges précédentes.

Règles de cardinalité :

  • Aucun label ne contient une URL, un terme de recherche ou un identifiant.
  • operation est un libellé fixe (centers.list, centers.get).
  • route_path est le chemin du fichier de route (/centres/[id]), pas l'URL visitée.

Les six indicateurs métier de la note initiale

IndicateurRequête PromQL
searches_totalsum(increase(saclay_research_searches_total[1d]))
successful_searches_totalsum(increase(saclay_research_searches_total{outcome="results"}[1d]))
zero_result_searches_totalsum(increase(saclay_research_searches_total{outcome="zero_results"}[1d]))
official_outbound_clicks_totalsum(increase(saclay_research_outbound_clicks_total{target!~"other|paris_saclay"}[1d]))
published_recordssum(saclay_research_published_records)
last_successful_sync_timestampmax(saclay_research_last_successful_sync_timestamp_seconds)

Les trois premiers sont réunis dans un seul compteur à label outcome, plus idiomatique pour Prometheus. Le taux de succès s'obtient par division :

Code
sum(rate(saclay_research_searches_total{outcome="results"}[1d])) / sum(rate(saclay_research_searches_total[1d]))

Avec plusieurs instances, chaque pod a ses propres compteurs : on les additionne toujours avec sum(...), comme dans les requêtes ci-dessus.

Comment une recherche est comptée

dataProvider est enveloppé par withSearchMetrics. Tout appel de liste avec un paramètre query non vide compte comme une recherche, quel que soit le fournisseur (fixtures, API amont ou un futur OpenSearch). Un appel de liste sans query, par exemple l'annuaire des centres, n'est pas une recherche. Rien n'est à ajouter dans les pages quand la recherche sera branchée.

Comment un clic sortant est compté

OutboundLinkTracker écoute les clics sur tout le document. Pour un lien externe, il envoie {"type":"outbound_click","target":"hal"} à /api/telemetry par navigator.sendBeacon.

  • Ce qui est envoyé : la catégorie seule, sans URL, cookie ni identifiant. Ce suivi n'est donc pas soumis au consentement et fonctionne même si Matomo est désactivé.
  • Ce que l'endpoint refuse : tout ce qui n'est pas une catégorie connue (400), et les corps de plus de 256 octets (413).

5. Matomo : mesure d'audience exemptée de consentement

Ce que fait le code

  • disableCookies : aucun cookie, donc aucun bandeau.
  • disableBrowserFeatureDetection : pas de collecte des plugins ou de la résolution (moins d'empreinte).
  • enableLinkTracking : liens sortants et téléchargements, dans les rapports natifs de Matomo.
  • Navigation côté client : une vue de page est envoyée à chaque changement de route, avec le référent précédent.
  • URL sans query string : les paramètres, dont q, ne sont jamais envoyés dans l'URL suivie.
  • Recherche interne : sur /recherche?q=…, la vue de page est remplacée par trackSiteSearch(terme, false, nbRésultats). C'est la recommandation de Matomo pour éviter le double comptage. Le terme passe d'abord par sanitizeSearchTerm, qui :
    • remplace par [filtré] les termes contenant une adresse e-mail, une URL, un ORCID ou une longue suite de chiffres ;
    • met en minuscules et tronque à 64 caractères.
  • Le rapport Comportement › Recherche interne › Mots-clés sans résultat est le premier outil d'amélioration éditoriale.

Les noms de personnes ne sont pas détectables automatiquement. Chercher « Jean Dupont » dans un annuaire public de chercheurs remonte « jean dupont » dans Matomo. C'est une donnée déjà publique sur la vitrine, mais la décision revient à la DPO : soit sanitized (par défaut), soit none.

Ce qu'il faut régler dans l'administration Matomo

Ces réglages sont à faire sur l'instance et ne dépendent pas du code. Ils sont à valider par la DPO au regard des lignes directrices de la CNIL sur les traceurs exemptés.

  • Anonymisation des IP : au moins 2 octets.
  • Durée de conservation : suppression automatique des données brutes, rapports et journaux au-delà de 25 mois au plus.
  • Fonctions désactivées : User ID, e-commerce et Heatmaps/Session Recording. Ces fonctions ne sont jamais utilisées par le code.
  • Isolement du site : pas de croisement avec d'autres sites ni de suivi multi-domaines.
  • Accès restreint : comptes nominatifs, réservés à la communication et au pilotage.
  • Paramètres d'URL exclus : q, page, utm_* si nécessaire. Ceinture et bretelles, puisque le code les retire déjà.
  • Recherche interne : activer « Recherche interne » sur le site.

Ce qui reste à faire côté site

  • Mécanisme d'opposition sur /donnees-personnelles : le module « Opt-out » de Matomo (Administration › Confidentialité › Opt-out des utilisateurs) fournit le code à intégrer. La CNIL demande que l'utilisateur puisse s'opposer à la mesure, même exemptée.
  • Texte d'information sur /donnees-personnelles : finalité, outil, hébergement, durée de conservation, droit d'opposition.
  • CSP : si une Content-Security-Policy est ajoutée dans next.config.ts, autoriser l'hôte Matomo dans script-src et connect-src.

Matomo en local

TerminalCode
docker compose -f ops/observability/docker-compose.yml --profile matomo up -d # http://localhost:8081 : assistant d'installation (base : matomo-db / matomo / matomo) # puis, dans .env.local : NEXT_PUBLIC_ANALYTICS_ENABLED=true NEXT_PUBLIC_MATOMO_URL=http://localhost:8081/ NEXT_PUBLIC_MATOMO_SITE_ID=1

En production, vérifier d'abord si la DSI de Paris-Saclay héberge déjà une instance Matomo : un site de plus suffit alors.


6. Prometheus, Grafana et Blackbox

En local

TerminalCode
# 1. L'application, avec le jeton attendu par la pile locale METRICS_TOKEN=local-dev-metrics-token pnpm dev # 2. La pile docker compose -f ops/observability/docker-compose.yml up -d
  • Grafana : http://localhost:3030 (admin / admin au premier lancement), dossier Saclay Research.
  • Prometheus : http://localhost:9090, menus Status › Targets et Alerts.
  • Vérification manuelle : curl -H "Authorization: Bearer local-dev-metrics-token" localhost:3000/api/metrics.

Le fichier prometheus/metrics-token ne contient qu'une valeur de développement. Ne jamais y mettre le jeton de production.

La pile locale ne contient pas d'Alertmanager : les alertes sont visibles dans l'onglet Alerts de Prometheus, sans notification.

En production (Kubernetes)

kube-prometheus-stack fournit en un seul chart Prometheus, Grafana, Alertmanager, les métriques du cluster, et des tableaux et alertes Kubernetes préconfigurés (documentation).

TerminalCode
# Une fois par cluster helm install monitoring oci://ghcr.io/prometheus-community/charts/kube-prometheus-stack \ --namespace monitoring --create-namespace helm install blackbox oci://ghcr.io/prometheus-community/charts/prometheus-blackbox-exporter \ --namespace monitoring # Dans le namespace de l'application kubectl create secret generic saclay-research-metrics --from-literal=token="$(openssl rand -hex 32)" # … injecter la même valeur dans le Deployment : env METRICS_TOKEN depuis ce Secret kubectl apply -k ops/observability

Hypothèses des manifestes, à adapter au chart de l'application :

  • Service : nommé saclay-research, label app.kubernetes.io/name: saclay-research, port nommé http.
  • Label release: monitoring : nom du release Helm, utilisé par défaut par le Prometheus du chart pour choisir ses ServiceMonitor, Probe et PrometheusRule.
  • URL de k8s/probe.yaml : à remplacer par le domaine public réel de la vitrine.
  • Tableaux : le sidecar Grafana du chart les charge depuis la ConfigMap saclay-research-dashboards (label grafana_dashboard: "1").

Hors Kubernetes, avec une VM et Docker, la même pile se déploie avec le docker-compose.yml adapté : cibles, jeton et volumes persistants.

Métriques HTTP par route (taux de 5xx, latence par chemin) : Next.js ne permet pas de mesurer proprement chaque réponse dans l'application. On les prend au niveau de l'ingress, qui les expose déjà :

  • ingress-nginx : nginx_ingress_controller_requests, nginx_ingress_controller_request_duration_seconds ;
  • Traefik : équivalents traefik_*.

kube-prometheus-stack les collecte une fois le ServiceMonitor de l'ingress activé.

Les trois tableaux

TableauContenu
Santé du serviceDisponibilité sur la période (cible 99,5 %), latence des sondes, erreurs serveur, version déployée, statuts et latence p95 de l'API amont, CPU, mémoire, boucle d'événements
Qualité éditorialeFiches publiées (total et par type), âge de la dernière synchronisation, rejets de contrat, révalidations
UsageRecherches, taux de succès, taux sans résultat, durée p95, sorties par source ; renvoi vers Matomo pour l'audience détaillée

Pour modifier un tableau, éditer grafana/build-dashboards.mjs, puis lancer pnpm ops:k8s. Les modifications faites dans l'interface Grafana ne sont pas conservées (allowUiUpdates: false). Un tableau modifié à la main peut être exporté en JSON puis reporté dans le générateur.

Alertes

AlerteConditionGravité
SaclayResearchIndisponibleSonde en échec pendant 5 mincritical
SaclayResearchLentSonde moyenne > 2 s pendant 10 minwarning
SaclayResearchMetriquesAbsentes/api/metrics illisible pendant 10 minwarning
SaclayResearchErreursServeurPlus de 5 erreurs serveur en 10 minwarning
SaclayResearchAmontEnErreurPlus de 20 % d'appels amont en 5xx ou erreur réseau, avec trafic minimalwarning
SaclayResearchContratRompuAu moins une réponse amont non conforme en 1 hwarning
SaclayResearchMemoireHauteMémoire résidente > 1,5 Go pendant 15 minwarning
SaclayResearchBoucleSatureeLatence p99 de la boucle d'événements > 0,5 s pendant 10 minwarning
SaclayResearchSynchroAncienneDernière synchronisation réussie il y a plus de 26 hwarning
SaclayResearchRecherchesSansResultatPlus de 30 % de recherches sans résultat sur 24 h, avec plus de 50 recherchesinfo

Le routage (e-mail, Mattermost, Tchap…) se configure dans l'Alertmanager de kube-prometheus-stack, à décider avec l'exploitant.


7. Logs et erreurs applicatives

Format

Chaque ligne est un objet JSON écrit sur stdout, ou sur stderr pour warn et error :

JSONCode
{ "time": "2026-10-24T05:10:27.105Z", "level": "error", "service": "saclay-research", "msg": "Réponse amont en erreur", "operation": "centers.get", "endpoint": "/centers/42", "status": 503 }

Messages émis aujourd'hui :

  • démarrage du serveur ;
  • erreurs serveur Next.js (onRequestError, avec digest, route, méthode et chemin sans query string) ;
  • appels amont en échec, réponses non conformes au contrat (10 premières anomalies Zod) ;
  • révalidations ;
  • échecs de collecte des métriques.

Ce qui n'est jamais journalisé :

  • les clés dont le nom contient token, secret, password, authorization, cookie ou email, remplacées par [redacted] ;
  • les query strings, donc les termes de recherche ;
  • les corps des requêtes de télémétrie.

Collecte : en Kubernetes, Grafana Alloy ou Promtail lit les logs des conteneurs et les envoie à Loki, interrogeable dans le même Grafana. Exemple de requête : {app="saclay-research"} | json | level="error". Même sans Loki, kubectl logs deploy/saclay-research | jq 'select(.level=="error")' suffit.

Erreurs : ce qui est branché

src/instrumentation.ts exporte onRequestError, le hook natif de Next.js. Chaque erreur de rendu, de route ou d'action :

  • incrémente request_errors_total{route_type, route_path} ;
  • produit une ligne level=error avec la pile d'appels.

C'est suffisant pour alerter et diagnostiquer, sans outil tiers.

Option GlitchTip / Sentry (non installée)

SENTRY_DSN et OTEL_EXPORTER_OTLP_ENDPOINT sont présents dans .env.example mais ne sont lus nulle part aujourd'hui.

Si un regroupement des erreurs avec historique et notifications devient nécessaire, la recommandation est GlitchTip, open source, auto-hébergeable et compatible avec le SDK Sentry. Il faudrait alors :

  1. ajouter @sentry/nextjs (respecter la politique des dépendances) ;
  2. l'initialiser dans register() avec :
    • sendDefaultPii: false ;
    • tracesSampleRate ≤ 0,1 ;
    • sans l'intégration Replay ;
  3. appeler Sentry.captureRequestError dans onRequestError, à côté du log existant ;
  4. compléter l'ADR 006.

La même logique vaut pour OpenTelemetry (@vercel/otel), si l'exploitant dispose d'un collecteur de traces.


8. Runbook : réagir aux alertes

Incident site indisponible

  1. Erreur réelle ou panne de sonde : curl -I https://<domaine>/api/health. Si l'appel réussit depuis l'extérieur, vérifier le Blackbox Exporter et le DNS.
  2. Pods : kubectl get pods, puis kubectl logs deploy/saclay-research --tail=200 | jq 'select(.level!="info")'.
  3. Déploiement récent : panneau « Version déployée ». En cas de doute, rollback sur l'image précédente (runbook § 1).

API amont en erreur

  • Les pages déjà générées restent servies par le cache (ADR 002) : pas d'urgence côté visiteurs.
  • Identifier l'opération touchée (tableau Santé, « Appels amont par statut »), puis prévenir l'exploitant de CRISalid avec l'heure de début et la classe d'erreur.

Contrat rompu

  • L'API a changé de format. Les logs Réponse amont non conforme au contrat listent les champs en cause.
  • Corriger le schéma dans src/contracts/ si le changement est légitime, ou signaler une régression à l'amont.

Synchronisation ancienne

  • Lire SELECT * FROM sync_runs ORDER BY started_at DESC LIMIT 10;, puis les colonnes status et error_log.
  • Relancer la synchronisation une fois la cause corrigée.

Recherches sans résultat

Il ne s'agit pas d'un incident mais d'un signal éditorial. Dans Matomo, ouvrir Comportement › Recherche interne › Mots-clés sans résultat. Selon les termes trouvés :

  • ajouter des synonymes ;
  • compléter les fiches ;
  • signaler une lacune de couverture au comité éditorial.

9. Rapport trimestriel

SectionSourceExtraction
Audience : visites, pages, provenance, appareilsMatomoRapport planifié par e-mail, ou export PDF/CSV
Recherches et recherches sans résultat (top 20)MatomoRecherche interne
Disponibilité, incidents, temps de réponseGrafana › SantéPériode = trimestre, export PDF ou capture
Fiches publiées, fraîcheur, rejetsGrafana › Qualité éditorialeIdem
Sorties vers HAL, ORCID, DOI…Grafana › UsageIdem

Ces indicateurs alimentent les jalons « continuer, pivoter ou arrêter » définis dans l'audit (page Exploitation et pilotage).


10. Limites connues et prochaines étapes

Limites des mesures actuelles

  • Recherche pas encore branchée : /recherche est un écran statique. Les métriques de recherche s'alimenteront dès qu'une liste sera appelée avec query ; SearchResults envoie déjà trackSiteSearch à Matomo.
  • Aucun job n'écrit encore dans sync_runs : last_successful_sync_timestamp_seconds reste absente tant que la synchronisation n'existe pas. L'alerte « synchro ancienne » ne se déclenche donc pas.
  • Fournisseur amont incomplet : il n'implémente que les centres. published_records n'affiche que les types réellement servis ; un type non servi est absent, pas à zéro.
  • /api/telemetry non authentifié (par nature) : un robot peut gonfler outbound_clicks_total. L'impact est faible, et une limite de débit à l'ingress suffit.
  • Métriques HTTP par route : elles viennent de l'ingress, pas de l'application (§ 6).

Points de configuration

  • Lint (Biome, biome.jsonc) : la règle « pas d'import de src/server » ne s'applique qu'aux dossiers clients (components, features, utils, theme, contracts). Avant, elle bloquait aussi les pages et le serveur, ce qui n'était pas son intention.
  • Tests unitaires : server-only est neutralisé dans vitest.config.ts pour pouvoir tester src/server.

Décisions à prendre

  • DPO : mode des termes de recherche (sanitized ou none), réglages Matomo (§ 5), texte et opt-out de /donnees-personnelles.
  • DSI : instance Matomo existante, cluster et chart de monitoring, routage des alertes, collecte des logs (Loki ou autre).
  • Cibles : 99,5 % de disponibilité, seuil de 26 h pour la synchronisation, 30 % de recherches sans résultat. Ce sont des valeurs de travail, à confirmer avec le pilotage.
  • ADR 006 : la passer de « Proposé » à « Accepté » une fois ces choix validés.