API — Parcours pédagogiques¶
Cette page documente les endpoints du parcours pédagogique de ChaosStudentController, montés sous /api/chaos/student/pedagogique. Ces endpoints gèrent un scénario d'apprentissage guidé où le formateur active un niveau BAC et les étudiants progressent à travers une série d'énigmes.
Contrôleur couvert
ChaosStudentController — partie parcours pédagogiques (activate, deactivate, hints, join, validate, sessions, last-summary, succes/{token}, level, logique/questions, logique/check, finale/validate)
La partie self-service du même contrôleur est documentée dans chaos-student.md.
Vue d'ensemble du modèle pédagogique¶
Un parcours pédagogique (ou « parcours BAC ») est une progression guidée en 5 niveaux de difficulté :
| Niveau | Surnom | Étoiles max | Format du code agent |
|---|---|---|---|
| bac1 | Junior | 1 ⭐ | 4 chiffres décimaux (ex: 3847) |
| bac2 | Confirmé | 2 ⭐ | 4 chiffres décimaux |
| bac3 | Expert | 3 ⭐ | 4 caractères hexadécimaux majuscules (ex: A3F7) |
| bac4 | Master | 4 ⭐ | 4 caractères hexadécimaux majuscules |
| bac5 | Maestro | 5 ⭐ | 4 caractères hexadécimaux majuscules |
Chaque niveau possède son propre catalogue d'énigmes dans PedagogiqueEnigme.forLevel(level). Tous les étudiants d'un même niveau voient exactement les mêmes énigmes dans le même ordre — le parcours n'est pas tiré aléatoirement. La personnalisation se fait sur deux axes :
- Le code agent — chaque étudiant reçoit son propre code dérivé de son UUID, qui sert à calculer la réponse à certaines énigmes « dynamiques ».
- Le tirage logique — pour le thème Logique & Mathématiques (clé finale du parcours), 5 questions sont tirées dans un pool de 25 via un LCG seedé par le token étudiant.
Flux général¶
sequenceDiagram
autonumber
participant F as Formateur
participant S as Étudiant
participant B as Backend
F->>B: POST /pedagogique/activate (level, memoryCache)
B-->>F: 200 — parcours actif
S->>B: POST /pedagogique/join
B-->>S: 200 — token, alias, totalSteps, timer
loop Pour chaque énigme
S->>B: POST /pedagogique/validate<br/>{step, answerHash}
B-->>S: 200 — {valid, nextEnigme|completed}
end
Note over S: Dernière étape validée
S->>B: GET /pedagogique/logique/questions
B-->>S: 5 questions logique tirées
S->>B: POST /pedagogique/logique/check<br/>{answerHashes[5]}
B-->>S: 200 — results[5]
S->>B: POST /pedagogique/finale/validate<br/>{theme, answerHash}
B-->>S: 200 — {valid, gameUrl?}
Authentification des endpoints¶
Les endpoints de ce contrôleur utilisent trois mécanismes d'authentification selon le rôle :
| Rôle | Mécanisme | Endpoints concernés |
|---|---|---|
| Formateur | X-Admin-Token ou session admin |
/activate, /deactivate, /hints, /sessions, /last-summary |
| Étudiant | header X-Student-Token (UUID unique par étudiant) |
/validate, /logique/questions, /logique/check, /finale/validate |
| Anonyme | Aucune | /join, /succes/{token}, /level |
Le X-Student-Token est généré par POST /pedagogique/join et doit être stocké côté client (typiquement dans localStorage). Chaque requête étudiante transporte ce header pour identifier sa session individuelle.
Liste des endpoints¶
| # | Méthode | Endpoint | Rôle |
|---|---|---|---|
| 1 | POST |
/api/chaos/student/pedagogique/activate |
Formateur — démarre un parcours |
| 2 | POST |
/api/chaos/student/pedagogique/deactivate |
Formateur — arrête le parcours |
| 3 | POST |
/api/chaos/student/pedagogique/hints |
Formateur — toggle les indices |
| 4 | GET |
/api/chaos/student/pedagogique/sessions |
Formateur — liste toutes les sessions étudiantes |
| 5 | GET |
/api/chaos/student/pedagogique/last-summary |
Formateur — snapshot du dernier parcours terminé |
| 6 | POST |
/api/chaos/student/pedagogique/join |
Étudiant — rejoint le parcours |
| 7 | POST |
/api/chaos/student/pedagogique/validate |
Étudiant — valide une énigme |
| 8 | POST |
/api/chaos/student/pedagogique/level |
Étudiant — change son niveau (self-service) |
| 9 | GET |
/api/chaos/student/pedagogique/succes/{token} |
Public — page de succès partageable |
| 10 | GET |
/api/chaos/student/pedagogique/logique/questions |
Étudiant — récupère ses 5 questions logique |
| 11 | POST |
/api/chaos/student/pedagogique/logique/check |
Étudiant — vérifie individuellement les 5 réponses |
| 12 | POST |
/api/chaos/student/pedagogique/finale/validate |
Étudiant — validation finale d'un thème |
POST /pedagogique/activate — Formateur¶
Active un parcours pédagogique au niveau BAC demandé. Remplace le parcours en cours et vide toutes les sessions étudiantes précédentes.
Auth : admin (session ou X-Admin-Token)
Licence : requiert une licence valide (402 LICENSE_REQUIRED sinon)
Requête¶
| Champ | Type | Requis | Description |
|---|---|---|---|
level |
string | oui | Identifiant du niveau : bac1, bac2, bac3, bac4 ou bac5 |
timerSeconds |
integer | non | Durée du parcours en secondes. Défaut = PedagogiqueEnigme.defaultTimerSeconds(level) |
memoryCache |
boolean | non | Active le cache mémoire write-through. Défaut : false |
Réponse — 200 OK¶
| Champ | Description |
|---|---|
level |
Niveau activé, tel qu'envoyé dans la requête |
totalSteps |
Nombre d'énigmes du niveau — déterminé par PedagogiqueEnigme.forLevel(level).size() |
timer |
Durée en secondes effectivement retenue |
memoryCacheEnabled |
État du cache mémoire après activation |
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | level hors de [bac1, bac2, bac3, bac4, bac5] (clé student.pedagogique.error.level_unknown) |
| 401 | Non authentifié admin |
| 402 | Aucune licence valide (LICENSE_REQUIRED) |
Cache mémoire write-through¶
Le paramètre memoryCache active une couche de cache en ConcurrentHashMap par-dessus la persistance DB. Quand activé :
- Chaque session créée est stockée à la fois en base (toujours) et en heap (cache)
- Les lectures consultent d'abord le cache (O(1) par session)
- Les objets
MutableSessionapparaissent dans les heap dumps générés par le Chaos Mémoire
La démonstration pédagogique associée consiste à activer memoryCache: true puis à générer un heap dump via /actuator/heapdump pour y retrouver les tokens, alias et progressions des étudiants — illustration concrète d'une fuite de données via fuite mémoire. En désactivé (défaut), les sessions ne survivent en heap que le temps de la requête.
POST /pedagogique/deactivate — Formateur¶
Arrête le parcours en cours et vide toutes les sessions étudiantes. Un snapshot est pris avant effacement et reste disponible via /last-summary.
Auth : admin
Réponse — 200 OK¶
Message localisé via student.pedagogique.deactivated.
Effets de bord¶
- Si des sessions existent,
buildSessionSnapshot()est appelé et remplace le contenu delastSessionSummary pedagogiqueActive = false, niveau remis ànull, timer remis à 0hintsEnabledremis àtrue(valeur par défaut pour le prochain parcours)memoryCacheremis àfalse(le prochain parcours devra le réactiver explicitement si besoin)pedagogiqueSessionService.clearAll()— toutes les lignes de la tablepedagogique_sessionssont supprimées
POST /pedagogique/hints — Formateur¶
Active ou désactive l'affichage du bouton 💡 indice dans l'interface étudiante, en temps réel pendant un parcours actif. L'état est propagé via le polling /status des étudiants (délai maximal ~15 secondes).
Auth : admin
Requête¶
| Champ | Type | Requis | Description |
|---|---|---|---|
enabled |
boolean | oui | true = indices visibles, false = bouton masqué |
Réponse — 200 OK¶
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | Champ enabled manquant ou non-booléen (clé student.pedagogique.error.hints_enabled_required) |
| 401 | Non authentifié admin |
GET /pedagogique/sessions — Formateur¶
Retourne la liste complète des sessions étudiantes du parcours actif, avec leur progression, leurs étoiles et leur durée. Endpoint admin — utilisé par le dashboard formateur.
Auth : admin
Réponse — 200 OK¶
{
"sessions": [
{
"token": "a7f1c2e8",
"alias": "Alice",
"step": 5,
"totalSteps": 8,
"joinedAt": 1712586600000,
"elapsedSeconds": 420,
"completed": false
},
{
"token": "b3d4e5f6",
"alias": "Agent-B3D4",
"step": 8,
"totalSteps": 8,
"joinedAt": 1712586640000,
"elapsedSeconds": 1680,
"completed": true,
"stars": 3,
"maxStars": 3
}
],
"count": 2,
"level": "bac3",
"timerRemaining": 720
}
| Champ | Description |
|---|---|
sessions[].token |
8 premiers caractères du token étudiant (troncature pour l'affichage) |
sessions[].alias |
Alias de l'étudiant, ou Agent-XXXX si non fourni (dérivé du token) |
sessions[].step |
Numéro de l'étape courante (0-indexé) |
sessions[].elapsedSeconds |
Temps écoulé depuis le /join de l'étudiant |
sessions[].completed |
true si step >= totalSteps |
sessions[].stars / maxStars |
Présents uniquement si completed: true — voir Règle des étoiles |
count |
Nombre total de sessions dans la liste |
level |
Niveau actif, ou chaîne vide si aucun parcours actif |
timerRemaining |
Secondes restantes sur le timer global du parcours, ou 0 |
Les sessions sont triées par joinedAt croissant (ordre d'arrivée).
Codes d'erreur¶
| Code | Cause |
|---|---|
| 401 | Non authentifié admin |
GET /pedagogique/last-summary — Formateur¶
Retourne le snapshot archivé du dernier parcours terminé. Contrairement à /sessions qui interroge les sessions vivantes, /last-summary expose l'état figé pris au moment de deactivate (ou d'un nouvel activate qui écrase le parcours en cours).
Auth : admin
Réponse — 200 OK¶
{
"sessions": [
{
"alias": "Alice",
"step": 8,
"totalSteps": 8,
"completed": true,
"joinedAt": 1712586640000,
"durationSeconds": 1680,
"stars": 3,
"maxStars": 3
}
],
"count": 1
}
Le snapshot est une copie immuable — il n'évolue pas jusqu'au prochain deactivate.
Codes d'erreur¶
| Code | Cause |
|---|---|
| 401 | Non authentifié admin |
Snapshot vide au démarrage
Après un redémarrage du backend, lastSessionSummary est initialisé à List.of() (liste vide). GET /last-summary retourne alors {sessions: [], count: 0} sans erreur.
POST /pedagogique/join — Étudiant¶
Point d'entrée anonyme pour un étudiant qui rejoint le parcours en cours. Génère un studentToken UUID unique et calcule le agentCode ainsi que les indices de questions logique.
Auth : aucune
Licence : requiert une licence valide (402 LICENSE_REQUIRED sinon)
Requête¶
Corps optionnel :
| Champ | Type | Requis | Description |
|---|---|---|---|
alias |
string | non | Nom d'affichage de l'étudiant. Si absent, génère Agent-XXXX à partir du token |
Un corps vide ou absent est accepté — l'étudiant sera identifié par Agent-<4 premiers chars>.
Réponse — 200 OK¶
{
"token": "a7f1c2e8-4b9d-4f11-9c8e-1a2b3c4d5e6f",
"alias": "Alice",
"level": "bac3",
"totalSteps": 8,
"timerRemaining": 2280,
"timerTotal": 2400
}
| Champ | Description |
|---|---|
token |
UUID à stocker côté client et à envoyer en X-Student-Token sur les appels suivants |
alias |
Alias retenu (éventuellement généré si non fourni) |
level |
Niveau actif du parcours (ex: bac3) |
totalSteps |
Nombre total d'énigmes du niveau |
timerRemaining |
Secondes restantes par rapport au timer global du parcours |
timerTotal |
Durée totale du parcours en secondes |
Le token est la seule clé de session
Le token UUID est la seule identité de l'étudiant — il n'y a pas de cookie, pas de session HTTP. Si le client le perd, la progression est inaccessible depuis l'étudiant (mais pas supprimée — le formateur peut toujours la voir dans /sessions).
Codes d'erreur¶
| Code | Cause |
|---|---|
| 402 | Aucune licence valide (LICENSE_REQUIRED) |
| 409 | Aucun parcours actif (clé student.pedagogique.error.no_active) |
| 429 | Limite de 500 sessions simultanées atteinte (clé student.pedagogique.error.too_many) |
Limite 500 sessions simultanées¶
PerfShop accepte jusqu'à 500 sessions actives en parallèle sur un même parcours. Cette limite protège contre un afflux accidentel qui saturerait la mémoire en mode cache. Un parcours normal de formation ne dépasse pas 60 étudiants. Au-delà de 500, le formateur doit désactiver puis réactiver le parcours pour libérer les sessions.
Ce qui est calculé au join¶
Au moment du /join, le service fait 4 calculs déterministes basés sur le token UUID :
- Code agent — 4 chiffres décimaux (bac1/bac2) ou 4 caractères hex majuscules (bac3/bac4/bac5) extraits du token
- Réponse dynamique — calculée par niveau (voir Code agent dynamique ci-dessous) puis hachée en SHA-256 (
extractionAnswerHash) - Indices logique — 5 indices distincts tirés dans
[0..24]via un LCG seedé par le token (identique à l'implémentation frontend) - Hash logique attendu — SHA-256 de la concaténation des 5 réponses correspondant aux indices
Ces 4 valeurs sont persistées dans la session (DB et cache) et utilisées par les endpoints /validate, /logique/* et /finale/validate sans jamais recalculer à la volée.
POST /pedagogique/validate — Étudiant¶
Valide la réponse à l'énigme courante. L'étudiant envoie un hash SHA-256 pré-calculé côté client, jamais la réponse en clair.
Auth : header X-Student-Token
Requête¶
| Champ | Type | Requis | Description |
|---|---|---|---|
step |
integer | oui | Numéro de l'étape 1-indexé (1 pour la première énigme) |
answerHash |
string | oui | SHA-256 en hexadécimal de la réponse normalisée (trim + lowercase + remove whitespace) |
Normalisation côté client¶
La normalisation de la réponse avant hash doit suivre exactement l'algorithme du backend :
function hashAnswer(answer) {
const normalized = answer.trim().toLowerCase().replace(/\s+/g, '');
// Puis SHA-256 hex du résultat
}
C'est la même normalisation que PedagogiqueSessionService.sha256() côté Java.
Réponse — 200 OK (bonne réponse, étape intermédiaire)¶
{
"valid": true,
"attempts": 1,
"completed": false,
"inTime": true,
"culturalNote": "Note culturelle optionnelle affichée au succès",
"nextEnigme": {
"id": "bac3-04",
"step": 4,
"text": "Texte de la prochaine énigme...",
"position": "bottom-right",
"hintAvailable": true,
"hint": "Indice contextuel",
"action": "observer"
}
}
| Champ | Description |
|---|---|
valid |
Toujours true pour cette variante |
attempts |
Nombre total de tentatives effectuées sur cette étape (incluant les échecs précédents) |
completed |
false s'il reste des étapes |
inTime |
true si le temps écoulé depuis pedagogiqueStartTime est ≤ timerTotal |
culturalNote |
Note culturelle optionnelle associée à l'énigme (présent uniquement si non null dans le catalogue) |
nextEnigme.step |
Numéro 1-indexé de l'étape suivante |
nextEnigme.hint |
Texte de l'indice si hintAvailable: true, sinon null |
Réponse — 200 OK (bonne réponse, parcours terminé)¶
Lorsque completed: true, session.completedAt est persisté en base et le nombre d'étoiles est calculé via computeStars(level, inTime).
Réponse — 200 OK (mauvaise réponse)¶
Message localisé via student.pedagogique.validate.wrong. La tentative est persistée en base (le compteur attempts survit au redémarrage), mais le step n'avance pas.
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | step hors de [1..totalSteps] (clé student.pedagogique.error.step_invalid) |
| 401 | Header X-Student-Token absent (clé student.pedagogique.error.token_missing) |
| 401 | Token inconnu ou session expirée (clé student.pedagogique.error.token_invalid) |
| 409 | Aucun parcours actif |
| 409 | step différent de session.step + 1 — tentative de saut ou de retour (clé student.pedagogique.error.step_wrong) — retourne {error, expected, submitted} |
Le hash de la réponse n'est jamais exposé
PedagogiqueEnigme ne renvoie jamais les answerHash au client. Pour les énigmes à réponse fixe, le hash est codé en dur dans le catalogue côté serveur. Pour les énigmes « DYNAMIC » (réponse dépendante du code agent), le hash est calculé au /join et stocké dans session.extractionAnswerHash — jamais retourné dans une réponse HTTP. L'étudiant doit donc réellement trouver la réponse, pas la deviner depuis le trafic réseau.
POST /pedagogique/level — Étudiant (self-service)¶
Permet à l'étudiant de choisir lui-même son niveau de parcours, sans passer par l'admin. Utile quand le formateur laisse la main au mode libre.
Auth : aucune (mais studentModeEnabled doit être actif)
Licence : requiert une licence valide
Requête¶
| Champ | Type | Requis | Description |
|---|---|---|---|
level |
integer | oui | Niveau numérique 0–5. 0 = sortir du parcours, 1..5 = activer le niveau correspondant |
Réponse — 200 OK (sortie du parcours)¶
Lorsque level: 0, le parcours est désactivé si actif, un snapshot est pris, toutes les sessions sont effacées :
Réponse — 200 OK (niveau déjà actif, même niveau)¶
Si le parcours demandé est exactement celui en cours, l'état courant est retourné sans réinitialiser :
{
"success": true,
"level": 3,
"levelId": "bac3",
"active": true,
"alreadyActive": true,
"totalSteps": 8,
"timerRemaining": 1800,
"timerTotal": 2400
}
Réponse — 200 OK (changement de niveau)¶
Si le niveau change (ou qu'aucun parcours n'était actif), le parcours est réinitialisé avec le timer par défaut du niveau :
{
"success": true,
"level": 3,
"levelId": "bac3",
"active": true,
"totalSteps": 8,
"timerRemaining": 2400,
"timerTotal": 2400
}
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | level null ou hors de [0..5] (clé student.pedagogique.error.level_range) |
| 400 | Niveau non reconnu côté PedagogiqueEnigme (clé student.pedagogique.error.level_unknown) |
| 402 | Aucune licence valide |
| 403 | Mode étudiant bloqué par le formateur (clé student.error.blocked) |
GET /pedagogique/succes/{token} — Public¶
Page de succès partageable — affiche le résultat d'un étudiant ayant terminé son parcours. Utilise le studentToken de l'étudiant comme identifiant public.
Auth : aucune
Paramètres¶
| Paramètre | Emplacement | Description |
|---|---|---|
token |
path | studentToken de l'étudiant (le même que celui utilisé en X-Student-Token) |
Réponse — 200 OK¶
{
"alias": "Alice",
"level": "bac3",
"stars": 3,
"maxStars": 3,
"durationSeconds": 1680,
"completedAt": 1712588400000,
"totalSteps": 8
}
| Champ | Description |
|---|---|
alias |
Alias de l'étudiant (ou Agent-XXXX) |
level |
Niveau du parcours terminé |
stars / maxStars |
Étoiles obtenues vs étoiles max du niveau |
durationSeconds |
Durée totale entre joinedAt et completedAt |
completedAt |
Timestamp epoch millis de la validation de la dernière étape |
totalSteps |
Nombre d'étapes du parcours |
Codes d'erreur¶
| Code | Cause |
|---|---|
| 404 | Token inconnu (clé student.pedagogique.error.session_not_found) |
| 404 | Session non terminée (step < totalSteps ou completedAt == 0) (clé student.pedagogique.error.not_completed) |
L'URL est publique mais opaque
L'endpoint n'est protégé par aucune authentification — le token dans l'URL fait office de secret partageable. C'est voulu : l'étudiant peut partager le lien /succes/{token} avec son formateur ou ses collègues. La sécurité repose sur l'imprévisibilité du UUID (128 bits aléatoires), pas sur un contrôle d'accès.
Thème Logique & Mathématiques (BAC5 final)¶
Le thème Logique est spécifique au parcours BAC5 final. Il consiste à répondre correctement à 5 questions de calcul mental, tirées dans un pool de 25, pour déverrouiller la dernière validation du parcours.
Deux endpoints sont dédiés à ce thème :
GET /pedagogique/logique/questions— récupère les 5 questions tirées pour l'étudiantPOST /pedagogique/logique/check— vérifie les réponses individuellement sans débloquer le parcours
GET /pedagogique/logique/questions — Étudiant¶
Retourne les 5 questions logique tirées au /join pour cet étudiant. Les indices sont calculés au join puis persistés — l'endpoint ne fait que les relire et matérialiser les textes depuis le pool i18n.
Auth : header X-Student-Token
Réponse — 200 OK¶
{
"questions": [
{ "index": 15, "text": "Combien de jours dans 7 mois de 31 jours ?", "hint": "31 × 7" },
{ "index": 10, "text": "Racine carrée de 144 ?", "hint": "12²" },
{ "index": 18, "text": "Minutes dans une journée ?", "hint": "24 × 60" },
{ "index": 19, "text": "Nombre de minutes dans une demi-heure ?", "hint": "60 ÷ 2" },
{ "index": 14, "text": "Combien font 3 × 13 ?", "hint": "Trois fois treize" }
]
}
| Champ | Description |
|---|---|
questions[].index |
Index de la question dans le pool de 25 (valeur 0..24) |
questions[].text |
Énoncé de la question dans la langue active (PERFSHOP_LANG) |
questions[].hint |
Indice d'aide au calcul |
Aucune réponse n'est jamais exposée par cet endpoint.
Codes d'erreur¶
| Code | Cause |
|---|---|
| 401 | Header X-Student-Token absent (clé student.pedagogique.token_missing) |
| 401 | Token inconnu ou invalide |
| 500 | Erreur interne — indices non calculés ou pool i18n introuvable (clé student.pedagogique.error.logique_indices) |
Source des questions¶
Le pool est chargé une seule fois au premier appel depuis i18n/logique/logique_<lang>.json (où <lang> est la valeur de la variable d'environnement PERFSHOP_LANG, défaut fr). Le fichier contient 25 paires {text, hint}. Les réponses associées sont stockées uniquement côté backend dans DefaultPedagogiqueSessionService.LOGIQUE_ANSWERS — un tableau Java en clair qui n'est jamais exposé par l'API.
Tirage déterministe par LCG¶
Le choix des 5 indices utilise un LCG (Linear Congruential Generator) seedé par le token UUID :
int seed = 0;
for (int i = 0; i < token.length(); i++) {
seed = (seed << 5) - seed + token.charAt(i);
}
// Puis pour chaque tirage :
seed = (seed * 1103515245 + 12345) & 0x7fffffff;
int index = seed % 25;
Le tirage continue jusqu'à obtenir 5 indices distincts. L'algorithme est identique à celui du frontend React (ThemeLogique.jsx) — c'est ce qui garantit la reproductibilité d'un même étudiant entre deux connexions.
POST /pedagogique/logique/check — Étudiant¶
Vérifie individuellement les 5 réponses hachées sans débloquer la validation finale. Permet à l'étudiant de savoir lesquelles de ses réponses sont fausses avant de tenter finale/validate.
Auth : header X-Student-Token
Requête¶
{
"answerHashes": [
"hash_sha256_réponse_1",
"hash_sha256_réponse_2",
"hash_sha256_réponse_3",
"hash_sha256_réponse_4",
"hash_sha256_réponse_5"
]
}
| Champ | Type | Requis | Description |
|---|---|---|---|
answerHashes |
array[string] | oui | Exactement 5 hashes SHA-256 hex, dans l'ordre des questions retournées par /logique/questions |
Réponse — 200 OK¶
Le tableau results contient 5 booléens alignés sur l'ordre des answerHashes envoyés. true indique une réponse correcte, false une réponse erronée.
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | Tableau answerHashes absent ou de taille ≠ 5 (clé student.pedagogique.error.logique_check_count) |
| 401 | Header X-Student-Token absent ou invalide |
| 500 | Indices logique non calculés (session pré-V38) |
Sécurité¶
L'endpoint est conçu pour ne jamais révéler les bonnes réponses :
- Les réponses stockées côté backend restent en Java (
LOGIQUE_ANSWERS) - Le serveur hache chaque bonne réponse et compare hash↔hash
- Le résultat est un simple booléen — pas de fuite de la valeur attendue
- L'endpoint est en lecture seule : il ne mute jamais la session
Le bénéfice pédagogique est purement ergonomique : éviter à l'étudiant de recommencer les 5 questions à cause d'une seule erreur, sans lui apprendre quoi que ce soit qu'il n'aurait pu deviner en tentant /finale/validate directement.
POST /pedagogique/finale/validate — Étudiant¶
Valide la réponse maîtresse d'un thème du parcours BAC5 final. L'étudiant appelle cet endpoint une fois par thème (5 thèmes au total). Chaque validation réussie retourne une URL de redirection.
Auth : header X-Student-Token
Requête¶
| Champ | Type | Requis | Description |
|---|---|---|---|
theme |
string | oui | Identifiant du thème : performance, fonctionnel, metier, securite ou logique |
answerHash |
string | oui | SHA-256 hex de la réponse normalisée |
Les 5 thèmes¶
| Thème | Origine de la réponse attendue |
|---|---|
performance |
Constante côté serveur — identique pour tous les étudiants |
fonctionnel |
Constante côté serveur |
metier |
Constante côté serveur |
securite |
Constante côté serveur |
logique |
Dynamique par étudiant — hash pré-calculé au /join et stocké dans session.logiqueExpectedHash |
Les 4 premiers thèmes ont des réponses fixes — elles sont les mêmes pour tous les étudiants quel que soit leur token. Seul logique est personnalisé : la réponse est la concaténation des 5 bonnes réponses aux questions tirées pour cet étudiant, dans l'ordre du tirage.
Réponse — 200 OK (succès)¶
Le champ gameUrl est une URL de redirection vers un hub de mini-jeux pédagogiques débloqué par la validation. La destination exacte dépend du thème validé : le thème logique pointe vers le hub principal, les autres thèmes pointent vers un jeu spécifique directement accessible.
À propos du hub de mini-jeux
Le hub est un espace distinct du backend PerfShop principal, configurable côté serveur via la propriété Spring PUBLIC_GAMES_URL. Son URL n'est volontairement pas documentée ici — elle apparaît uniquement dans la réponse finale/validate, comme une récompense de fin de parcours. Les formateurs qui déploient PerfShop en interne peuvent pointer cette propriété vers leur propre hub de jeux ou vers l'hébergement officiel selon leur contexte.
Réponse — 200 OK (échec)¶
Aucun message, aucune indication de la raison de l'échec. L'étudiant doit simplement retenter.
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | Thème inconnu (clé student.pedagogique.error.theme_unknown) |
| 401 | Header X-Student-Token absent ou invalide |
Le code agent dynamique¶
Chaque étudiant reçoit un code agent unique calculé au /join à partir de son token UUID. Ce code sert à deux choses :
- Calcul de la réponse dynamique pour les énigmes marquées
DYNAMICdans le catalogue - Identification narrative dans les énigmes du parcours BAC5 (agents secrets)
Format du code selon le niveau¶
| Niveau | Format | Extraction depuis le token |
|---|---|---|
| bac1, bac2 | 4 chiffres décimaux | Tous les chiffres du token UUID, 4 premiers retenus |
| bac3, bac4, bac5 | 4 caractères hex majuscules | 4 premiers caractères du token après retrait des tirets, en majuscules |
Le choix du format est pédagogique : les étudiants de niveau BAC1/BAC2 ne maîtrisent pas nécessairement l'hexadécimal, donc leur code reste décimal. Dès BAC3, la manipulation hex fait partie des compétences attendues.
Calcul de la réponse dynamique par niveau¶
À partir du code agent, la réponse à une énigme DYNAMIC est calculée par DefaultPedagogiqueSessionService.computeDynamicAnswer() selon l'algorithme du niveau :
| Niveau | Algorithme | Exemple (code → réponse) |
|---|---|---|
| bac1 | Somme des 4 chiffres × 10 | 3847 → (3+8+4+7) × 10 = 220 |
| bac2 | Code décimal ÷ somme des 4 chiffres (division entière) | 3847 → 3847 / 22 = 174 |
| bac3 | Somme des valeurs décimales des 4 digits hex | A3F7 → 10 + 3 + 15 + 7 = 35 |
| bac4 | Valeur décimale du code hex, mod 256 | A3F7 → 41975 mod 256 = 55 |
| bac5 | XOR de l'octet haut et de l'octet bas | A3F7 → 0xA3 ^ 0xF7 = 84 |
La réponse en clair est ensuite hachée en SHA-256 et stockée dans session.extractionAnswerHash. C'est ce hash qui est comparé au answerHash reçu lors de POST /validate pour les énigmes dynamiques.
Règle des étoiles¶
Le nombre d'étoiles gagnées en terminant le parcours dépend du niveau et du respect du temps imparti :
| Niveau | Étoiles dans les temps | Étoiles hors délai |
|---|---|---|
| bac1 | 1 ⭐ | 1 ⭐ (minimum garanti) |
| bac2 | 2 ⭐ | 1 ⭐ |
| bac3 | 3 ⭐ | 2 ⭐ |
| bac4 | 4 ⭐ | 3 ⭐ |
| bac5 | 5 ⭐ | 4 ⭐ |
L'évaluation inTime compare (completedAt - joinedAt) en secondes au timerTotal. Un étudiant qui termine à la seconde près est considéré « dans les temps ».
Exemple curl — parcours complet BAC3¶
# === Formateur ===
TOKEN_ADMIN=$(curl -s -H "Content-Type: application/json" \
-d '{"email":"admin@perfshop.fr","password":"admin"}' \
http://localhost:9080/api/admin/login | jq -r '.adminToken')
# 1. Active le parcours BAC3 avec cache mémoire (pour démo heap dump)
curl -H "X-Admin-Token: $TOKEN_ADMIN" -H "Content-Type: application/json" \
-d '{"level":"bac3","memoryCache":true}' \
http://localhost:9080/api/chaos/student/pedagogique/activate
# === Étudiant ===
# 2. Rejoint le parcours — récupère son token
RESPONSE=$(curl -s -H "Content-Type: application/json" \
-d '{"alias":"Alice"}' \
http://localhost:9080/api/chaos/student/pedagogique/join)
STUDENT=$(echo $RESPONSE | jq -r '.token')
echo "Mon token : $STUDENT"
# 3. Calculer le hash côté client puis valider l'étape 1
# Exemple bash avec openssl — normalisation trim+lower+nospace
ANSWER="ma_reponse"
NORMALIZED=$(echo -n "$ANSWER" | tr '[:upper:]' '[:lower:]' | tr -d ' \t\n')
HASH=$(echo -n "$NORMALIZED" | openssl dgst -sha256 | awk '{print $2}')
curl -H "X-Student-Token: $STUDENT" \
-H "Content-Type: application/json" \
-d "{\"step\":1,\"answerHash\":\"$HASH\"}" \
http://localhost:9080/api/chaos/student/pedagogique/validate
# 4. (… valider les autres étapes une par une …)
# 5. Consulter sa page de succès (lien partageable)
curl "http://localhost:9080/api/chaos/student/pedagogique/succes/$STUDENT"
# === Formateur : consulter les sessions vivantes ===
curl -H "X-Admin-Token: $TOKEN_ADMIN" \
http://localhost:9080/api/chaos/student/pedagogique/sessions
# === Formateur : arrêt ===
curl -H "X-Admin-Token: $TOKEN_ADMIN" -X POST \
http://localhost:9080/api/chaos/student/pedagogique/deactivate
Points d'attention¶
Persistance DB vs cache mémoire
En mode memoryCache: true, les sessions sont écrites à la fois en cache et en DB. En cas de crash du backend, le cache est perdu mais les sessions sont reconstructibles depuis la table pedagogique_sessions. Un redémarrage n'invalide pas les tokens étudiants — c'est voulu pour éviter les pertes de progression.
Rejouabilité des énigmes
Les énigmes du catalogue sont fixes par niveau — tous les étudiants voient les mêmes questions dans le même ordre. Ce qui varie par étudiant, c'est :
- Le code agent et la réponse dynamique des énigmes
DYNAMIC - Le tirage des 5 questions logique sur 25 (BAC5 uniquement)
Cette personnalisation ciblée empêche le partage direct de réponses entre étudiants voisins tout en gardant un parcours identique pour la progression et l'évaluation.
Protection anti-triche
Trois mesures empêchent la triche :
- Les réponses ne sont jamais exposées — ni en clair, ni en hash, par aucun endpoint
- Les hash sont calculés côté client — l'étudiant n'apprend rien en écoutant le trafic réseau
- Le code agent est unique — même si un étudiant partage sa réponse dynamique, elle n'est valide que pour son propre code
Liens associés¶
chaos-student.md— pilotage self-service (même contrôleur)orders.md— injection duagentCodedans la réponse de commandechaos-admin.md— pilotage chaos formateur- Catalogue des énigmes — liste complète BAC1–BAC5
- Code agent dynamique — détails des formules