API — Pedagogical journeys¶
This page documents the pedagogical journey endpoints of ChaosStudentController, mounted under /api/chaos/student/pedagogique. These endpoints handle a guided learning scenario where the instructor activates a BAC level and students progress through a series of enigmas.
Controller covered
ChaosStudentController — pedagogical journeys part (activate, deactivate, hints, join, validate, sessions, last-summary, succes/{token}, level, logique/questions, logique/check, finale/validate)
The self-service part of the same controller is documented in chaos-student.md.
Pedagogical model overview¶
A pedagogical journey (or "BAC journey") is a guided progression across 5 difficulty levels:
| Level | Nickname | Max stars | Agent code format |
|---|---|---|---|
| bac1 | Junior | 1 ⭐ | 4 decimal digits (e.g. 3847) |
| bac2 | Intermediate | 2 ⭐ | 4 decimal digits |
| bac3 | Expert | 3 ⭐ | 4 uppercase hexadecimal characters (e.g. A3F7) |
| bac4 | Master | 4 ⭐ | 4 uppercase hexadecimal characters |
| bac5 | Maestro | 5 ⭐ | 4 uppercase hexadecimal characters |
Each level has its own enigma catalog in PedagogiqueEnigme.forLevel(level). All students on the same level see exactly the same enigmas in the same order — the journey is not randomly drawn. Personalization happens on two axes:
- The agent code — each student receives their own code derived from their UUID, used to compute the answer to certain "dynamic" enigmas.
- The logic draw — for the Logic & Mathematics theme (final key of the journey), 5 questions are drawn from a pool of 25 via an LCG seeded by the student token.
General flow¶
sequenceDiagram
autonumber
participant F as Instructor
participant S as Student
participant B as Backend
F->>B: POST /pedagogique/activate (level, memoryCache)
B-->>F: 200 — journey active
S->>B: POST /pedagogique/join
B-->>S: 200 — token, alias, totalSteps, timer
loop For each enigma
S->>B: POST /pedagogique/validate<br/>{step, answerHash}
B-->>S: 200 — {valid, nextEnigme|completed}
end
Note over S: Last step validated
S->>B: GET /pedagogique/logique/questions
B-->>S: 5 logic questions drawn
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?}
Endpoint authentication¶
The endpoints of this controller use three mechanisms of authentication depending on the role:
| Role | Mechanism | Endpoints concerned |
|---|---|---|
| Instructor | X-Admin-Token or admin session |
/activate, /deactivate, /hints, /sessions, /last-summary |
| Student | X-Student-Token header (unique UUID per student) |
/validate, /logique/questions, /logique/check, /finale/validate |
| Anonymous | None | /join, /succes/{token}, /level |
The X-Student-Token is generated by POST /pedagogique/join and must be stored client-side (typically in localStorage). Every student request carries this header to identify their individual session.
Endpoint list¶
| # | Method | Endpoint | Role |
|---|---|---|---|
| 1 | POST |
/api/chaos/student/pedagogique/activate |
Instructor — starts a journey |
| 2 | POST |
/api/chaos/student/pedagogique/deactivate |
Instructor — stops the journey |
| 3 | POST |
/api/chaos/student/pedagogique/hints |
Instructor — toggle hints |
| 4 | GET |
/api/chaos/student/pedagogique/sessions |
Instructor — lists all student sessions |
| 5 | GET |
/api/chaos/student/pedagogique/last-summary |
Instructor — snapshot of the last completed journey |
| 6 | POST |
/api/chaos/student/pedagogique/join |
Student — joins the journey |
| 7 | POST |
/api/chaos/student/pedagogique/validate |
Student — validates an enigma |
| 8 | POST |
/api/chaos/student/pedagogique/level |
Student — changes their level (self-service) |
| 9 | GET |
/api/chaos/student/pedagogique/succes/{token} |
Public — shareable success page |
| 10 | GET |
/api/chaos/student/pedagogique/logique/questions |
Student — retrieves their 5 logic questions |
| 11 | POST |
/api/chaos/student/pedagogique/logique/check |
Student — checks the 5 answers individually |
| 12 | POST |
/api/chaos/student/pedagogique/finale/validate |
Student — final validation of a theme |
POST /pedagogique/activate — Instructor¶
Activates a pedagogical journey at the requested BAC level. Replaces the current journey and empties all previous student sessions.
Auth: admin (session or X-Admin-Token)
License: requires a valid license (402 LICENSE_REQUIRED otherwise)
Request¶
| Field | Type | Required | Description |
|---|---|---|---|
level |
string | yes | Level identifier: bac1, bac2, bac3, bac4 or bac5 |
timerSeconds |
integer | no | Journey duration in seconds. Default = PedagogiqueEnigme.defaultTimerSeconds(level) |
memoryCache |
boolean | no | Enables the write-through memory cache. Default: false |
Response — 200 OK¶
| Field | Description |
|---|---|
level |
Activated level, as sent in the request |
totalSteps |
Number of enigmas for the level — determined by PedagogiqueEnigme.forLevel(level).size() |
timer |
Effectively retained duration in seconds |
memoryCacheEnabled |
State of the memory cache after activation |
Error codes¶
| Code | Cause |
|---|---|
| 400 | level outside [bac1, bac2, bac3, bac4, bac5] (key student.pedagogique.error.level_unknown) |
| 401 | Admin not authenticated |
| 402 | No valid license (LICENSE_REQUIRED) |
Write-through memory cache¶
The memoryCache parameter enables a ConcurrentHashMap cache layer on top of DB persistence. When enabled:
- Each session created is stored both in the database (always) and in the heap (cache)
- Reads consult the cache first (O(1) per session)
MutableSessionobjects appear in heap dumps generated by Memory chaos
The associated pedagogical demonstration consists of enabling memoryCache: true then generating a heap dump via /actuator/heapdump to find the tokens, aliases and progressions of students in it — a concrete illustration of data leakage via memory leak. When disabled (default), sessions only survive in the heap for the duration of the request.
POST /pedagogique/deactivate — Instructor¶
Stops the current journey and empties all student sessions. A snapshot is taken before erasure and remains available via /last-summary.
Auth: admin
Response — 200 OK¶
Message localized via student.pedagogique.deactivated.
Side effects¶
- If sessions exist,
buildSessionSnapshot()is called and replaces the content oflastSessionSummary pedagogiqueActive = false, level reset tonull, timer reset to 0hintsEnabledreset totrue(default value for the next journey)memoryCachereset tofalse(the next journey will need to re-enable it explicitly if needed)pedagogiqueSessionService.clearAll()— all rows in thepedagogique_sessionstable are deleted
POST /pedagogique/hints — Instructor¶
Enables or disables the display of the 💡 hint button in the student interface, in real time during an active journey. The state is propagated via the students' /status polling (maximum delay ~15 seconds).
Auth: admin
Request¶
| Field | Type | Required | Description |
|---|---|---|---|
enabled |
boolean | yes | true = hints visible, false = button hidden |
Response — 200 OK¶
Error codes¶
| Code | Cause |
|---|---|
| 400 | enabled field missing or non-boolean (key student.pedagogique.error.hints_enabled_required) |
| 401 | Admin not authenticated |
GET /pedagogique/sessions — Instructor¶
Returns the full list of student sessions in the active journey, with their progression, stars and duration. Admin endpoint — used by the instructor dashboard.
Auth: admin
Response — 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
}
| Field | Description |
|---|---|
sessions[].token |
First 8 characters of the student token (truncation for display) |
sessions[].alias |
Student alias, or Agent-XXXX if not provided (derived from the token) |
sessions[].step |
Current step number (0-indexed) |
sessions[].elapsedSeconds |
Time elapsed since the student's /join |
sessions[].completed |
true if step >= totalSteps |
sessions[].stars / maxStars |
Present only if completed: true — see Stars rule |
count |
Total number of sessions in the list |
level |
Active level, or empty string if no active journey |
timerRemaining |
Seconds remaining on the global journey timer, or 0 |
Sessions are sorted by ascending joinedAt (arrival order).
Error codes¶
| Code | Cause |
|---|---|
| 401 | Admin not authenticated |
GET /pedagogique/last-summary — Instructor¶
Returns the archived snapshot of the last completed journey. Unlike /sessions which queries live sessions, /last-summary exposes the frozen state taken at the moment of deactivate (or of a new activate that overwrites the current journey).
Auth: admin
Response — 200 OK¶
{
"sessions": [
{
"alias": "Alice",
"step": 8,
"totalSteps": 8,
"completed": true,
"joinedAt": 1712586640000,
"durationSeconds": 1680,
"stars": 3,
"maxStars": 3
}
],
"count": 1
}
The snapshot is an immutable copy — it does not evolve until the next deactivate.
Error codes¶
| Code | Cause |
|---|---|
| 401 | Admin not authenticated |
Empty snapshot on startup
After a backend restart, lastSessionSummary is initialized to List.of() (empty list). GET /last-summary then returns {sessions: [], count: 0} without error.
POST /pedagogique/join — Student¶
Anonymous entry point for a student joining the current journey. Generates a unique UUID studentToken and computes the agentCode as well as the logic question indices.
Auth: none
License: requires a valid license (402 LICENSE_REQUIRED otherwise)
Request¶
Optional body:
| Field | Type | Required | Description |
|---|---|---|---|
alias |
string | no | Student display name. If absent, generates Agent-XXXX from the token |
An empty or missing body is accepted — the student will be identified by Agent-<first 4 chars>.
Response — 200 OK¶
{
"token": "a7f1c2e8-4b9d-4f11-9c8e-1a2b3c4d5e6f",
"alias": "Alice",
"level": "bac3",
"totalSteps": 8,
"timerRemaining": 2280,
"timerTotal": 2400
}
| Field | Description |
|---|---|
token |
UUID to store client-side and send as X-Student-Token on subsequent calls |
alias |
Retained alias (possibly generated if not provided) |
level |
Active journey level (e.g. bac3) |
totalSteps |
Total number of enigmas for the level |
timerRemaining |
Seconds remaining relative to the global journey timer |
timerTotal |
Total journey duration in seconds |
The token is the only session key
The UUID token is the only student identity — there is no cookie, no HTTP session. If the client loses it, the progression is inaccessible from the student's side (but not deleted — the instructor can still see it in /sessions).
Error codes¶
| Code | Cause |
|---|---|
| 402 | No valid license (LICENSE_REQUIRED) |
| 409 | No active journey (key student.pedagogique.error.no_active) |
| 429 | 500 simultaneous sessions limit reached (key student.pedagogique.error.too_many) |
500 simultaneous sessions limit¶
PerfShop accepts up to 500 active sessions in parallel on the same journey. This limit protects against an accidental surge that would saturate memory in cache mode. A normal training journey does not exceed 60 students. Beyond 500, the instructor must deactivate then reactivate the journey to free the sessions.
What is computed at join¶
At the moment of /join, the service performs 4 deterministic computations based on the UUID token:
- Agent code — 4 decimal digits (bac1/bac2) or 4 uppercase hex characters (bac3/bac4/bac5) extracted from the token
- Dynamic answer — computed per level (see The dynamic agent code below) then hashed with SHA-256 (
extractionAnswerHash) - Logic indices — 5 distinct indices drawn from
[0..24]via an LCG seeded by the token (identical to the frontend implementation) - Expected logic hash — SHA-256 of the concatenation of the 5 answers corresponding to the indices
These 4 values are persisted in the session (DB and cache) and used by the /validate, /logique/* and /finale/validate endpoints without ever recomputing on the fly.
POST /pedagogique/validate — Student¶
Validates the answer to the current enigma. The student sends a SHA-256 hash pre-computed client-side, never the plain-text answer.
Auth: X-Student-Token header
Request¶
| Field | Type | Required | Description |
|---|---|---|---|
step |
integer | yes | Step number 1-indexed (1 for the first enigma) |
answerHash |
string | yes | SHA-256 in hexadecimal of the normalized answer (trim + lowercase + remove whitespace) |
Client-side normalization¶
Answer normalization before hashing must exactly match the backend algorithm:
function hashAnswer(answer) {
const normalized = answer.trim().toLowerCase().replace(/\s+/g, '');
// Then SHA-256 hex of the result
}
This is the same normalization as PedagogiqueSessionService.sha256() on the Java side.
Response — 200 OK (correct answer, intermediate step)¶
{
"valid": true,
"attempts": 1,
"completed": false,
"inTime": true,
"culturalNote": "Optional cultural note displayed on success",
"nextEnigme": {
"id": "bac3-04",
"step": 4,
"text": "Text of the next enigma...",
"position": "bottom-right",
"hintAvailable": true,
"hint": "Contextual hint",
"action": "observer"
}
}
| Field | Description |
|---|---|
valid |
Always true for this variant |
attempts |
Total number of attempts made on this step (including previous failures) |
completed |
false if steps remain |
inTime |
true if the time elapsed since pedagogiqueStartTime is ≤ timerTotal |
culturalNote |
Optional cultural note associated with the enigma (present only if not null in the catalog) |
nextEnigme.step |
1-indexed number of the next step |
nextEnigme.hint |
Hint text if hintAvailable: true, otherwise null |
Response — 200 OK (correct answer, journey completed)¶
When completed: true, session.completedAt is persisted to the database and the number of stars is computed via computeStars(level, inTime).
Response — 200 OK (wrong answer)¶
Message localized via student.pedagogique.validate.wrong. The attempt is persisted to the database (the attempts counter survives restart), but the step does not advance.
Error codes¶
| Code | Cause |
|---|---|
| 400 | step outside [1..totalSteps] (key student.pedagogique.error.step_invalid) |
| 401 | X-Student-Token header missing (key student.pedagogique.error.token_missing) |
| 401 | Unknown token or expired session (key student.pedagogique.error.token_invalid) |
| 409 | No active journey |
| 409 | step different from session.step + 1 — attempted skip or backtrack (key student.pedagogique.error.step_wrong) — returns {error, expected, submitted} |
The answer hash is never exposed
PedagogiqueEnigme never returns the answerHash to the client. For fixed-answer enigmas, the hash is hardcoded in the server-side catalog. For "DYNAMIC" enigmas (answer dependent on the agent code), the hash is computed at /join and stored in session.extractionAnswerHash — never returned in an HTTP response. The student must therefore actually find the answer, not guess it from network traffic.
POST /pedagogique/level — Student (self-service)¶
Lets the student choose their own journey level, without going through admin. Useful when the instructor hands over to free mode.
Auth: none (but studentModeEnabled must be active)
License: requires a valid license
Request¶
| Field | Type | Required | Description |
|---|---|---|---|
level |
integer | yes | Numeric level 0–5. 0 = exit the journey, 1..5 = activate the corresponding level |
Response — 200 OK (exit journey)¶
When level: 0, the journey is deactivated if active, a snapshot is taken, all sessions are erased:
Response — 200 OK (level already active, same level)¶
If the requested journey is exactly the current one, the current state is returned without reset:
{
"success": true,
"level": 3,
"levelId": "bac3",
"active": true,
"alreadyActive": true,
"totalSteps": 8,
"timerRemaining": 1800,
"timerTotal": 2400
}
Response — 200 OK (level change)¶
If the level changes (or if no journey was active), the journey is reset with the level's default timer:
{
"success": true,
"level": 3,
"levelId": "bac3",
"active": true,
"totalSteps": 8,
"timerRemaining": 2400,
"timerTotal": 2400
}
Error codes¶
| Code | Cause |
|---|---|
| 400 | level null or outside [0..5] (key student.pedagogique.error.level_range) |
| 400 | Level not recognized on the PedagogiqueEnigme side (key student.pedagogique.error.level_unknown) |
| 402 | No valid license |
| 403 | Student mode locked by the instructor (key student.error.blocked) |
GET /pedagogique/succes/{token} — Public¶
Shareable success page — displays the result of a student who has completed their journey. Uses the student's studentToken as the public identifier.
Auth: none
Parameters¶
| Parameter | Location | Description |
|---|---|---|
token |
path | Student's studentToken (the same one used as X-Student-Token) |
Response — 200 OK¶
{
"alias": "Alice",
"level": "bac3",
"stars": 3,
"maxStars": 3,
"durationSeconds": 1680,
"completedAt": 1712588400000,
"totalSteps": 8
}
| Field | Description |
|---|---|
alias |
Student alias (or Agent-XXXX) |
level |
Level of the completed journey |
stars / maxStars |
Earned stars vs max stars for the level |
durationSeconds |
Total duration between joinedAt and completedAt |
completedAt |
Epoch millis timestamp of the last step validation |
totalSteps |
Number of steps in the journey |
Error codes¶
| Code | Cause |
|---|---|
| 404 | Unknown token (key student.pedagogique.error.session_not_found) |
| 404 | Session not completed (step < totalSteps or completedAt == 0) (key student.pedagogique.error.not_completed) |
The URL is public but opaque
The endpoint is protected by no authentication — the token in the URL acts as a shareable secret. This is intentional: the student can share the /succes/{token} link with their instructor or colleagues. Security relies on the unpredictability of the UUID (128 random bits), not on access control.
Logic & Mathematics theme (BAC5 final)¶
The Logic theme is specific to the BAC5 final journey. It consists of correctly answering 5 mental-calculation questions, drawn from a pool of 25, to unlock the final validation of the journey.
Two endpoints are dedicated to this theme:
GET /pedagogique/logique/questions— retrieves the 5 questions drawn for the studentPOST /pedagogique/logique/check— checks the answers individually without unlocking the journey
GET /pedagogique/logique/questions — Student¶
Returns the 5 logic questions drawn at /join for this student. The indices are computed at join then persisted — the endpoint only re-reads them and materializes the texts from the i18n pool.
Auth: X-Student-Token header
Response — 200 OK¶
{
"questions": [
{ "index": 15, "text": "How many days in 7 months of 31 days?", "hint": "31 × 7" },
{ "index": 10, "text": "Square root of 144?", "hint": "12²" },
{ "index": 18, "text": "Minutes in a day?", "hint": "24 × 60" },
{ "index": 19, "text": "Number of minutes in half an hour?", "hint": "60 ÷ 2" },
{ "index": 14, "text": "What is 3 × 13?", "hint": "Three times thirteen" }
]
}
| Field | Description |
|---|---|
questions[].index |
Index of the question in the pool of 25 (value 0..24) |
questions[].text |
Question text in the active language (PERFSHOP_LANG) |
questions[].hint |
Computation hint |
No answer is ever exposed by this endpoint.
Error codes¶
| Code | Cause |
|---|---|
| 401 | X-Student-Token header missing (key student.pedagogique.token_missing) |
| 401 | Unknown or invalid token |
| 500 | Internal error — indices not computed or i18n pool not found (key student.pedagogique.error.logique_indices) |
Source of the questions¶
The pool is loaded only once on the first call from i18n/logique/logique_<lang>.json (where <lang> is the value of the PERFSHOP_LANG environment variable, default fr). The file contains 25 {text, hint} pairs. The associated answers are stored only server-side in DefaultPedagogiqueSessionService.LOGIQUE_ANSWERS — a plain Java array never exposed by the API.
Deterministic draw by LCG¶
The choice of the 5 indices uses an LCG (Linear Congruential Generator) seeded by the UUID token:
int seed = 0;
for (int i = 0; i < token.length(); i++) {
seed = (seed << 5) - seed + token.charAt(i);
}
// Then for each draw:
seed = (seed * 1103515245 + 12345) & 0x7fffffff;
int index = seed % 25;
The draw continues until 5 distinct indices are obtained. The algorithm is identical to the React frontend one (ThemeLogique.jsx) — this is what guarantees reproducibility for a single student across two connections.
POST /pedagogique/logique/check — Student¶
Individually verifies the 5 hashed answers without unlocking the final validation. Lets the student know which of their answers are wrong before attempting finale/validate.
Auth: X-Student-Token header
Request¶
{
"answerHashes": [
"hash_sha256_answer_1",
"hash_sha256_answer_2",
"hash_sha256_answer_3",
"hash_sha256_answer_4",
"hash_sha256_answer_5"
]
}
| Field | Type | Required | Description |
|---|---|---|---|
answerHashes |
array[string] | yes | Exactly 5 hex SHA-256 hashes, in the order of the questions returned by /logique/questions |
Response — 200 OK¶
The results array contains 5 booleans aligned on the order of the answerHashes sent. true indicates a correct answer, false an incorrect one.
Error codes¶
| Code | Cause |
|---|---|
| 400 | answerHashes array missing or of size ≠ 5 (key student.pedagogique.error.logique_check_count) |
| 401 | X-Student-Token header missing or invalid |
| 500 | Logic indices not computed (pre-V38 session) |
Security¶
The endpoint is designed to never reveal the correct answers:
- Answers stored server-side stay in Java (
LOGIQUE_ANSWERS) - The server hashes each correct answer and compares hash↔hash
- The result is a simple boolean — no leak of the expected value
- The endpoint is read-only: it never mutates the session
The pedagogical benefit is purely ergonomic: sparing the student from restarting all 5 questions because of a single error, without teaching them anything they couldn't have guessed by trying /finale/validate directly.
POST /pedagogique/finale/validate — Student¶
Validates the master answer for one theme of the BAC5 final journey. The student calls this endpoint once per theme (5 themes total). Each successful validation returns a redirect URL.
Auth: X-Student-Token header
Request¶
| Field | Type | Required | Description |
|---|---|---|---|
theme |
string | yes | Theme identifier: performance, fonctionnel, metier, securite or logique |
answerHash |
string | yes | Hex SHA-256 of the normalized answer |
The 5 themes¶
| Theme | Origin of the expected answer |
|---|---|
performance |
Server-side constant — identical for all students |
fonctionnel |
Server-side constant |
metier |
Server-side constant |
securite |
Server-side constant |
logique |
Dynamic per student — hash pre-computed at /join and stored in session.logiqueExpectedHash |
The first 4 themes have fixed answers — they are the same for all students regardless of token. Only logique is personalized: the answer is the concatenation of the 5 correct answers to the questions drawn for that student, in draw order.
Response — 200 OK (success)¶
The gameUrl field is a redirect URL to a pedagogical games hub unlocked by the validation. The exact destination depends on the validated theme: the logique theme points to the main hub, the other themes point to a specific game directly accessible.
About the games hub
The hub is a space separate from the main PerfShop backend, server-side configurable via the Spring property PUBLIC_GAMES_URL. Its URL is intentionally not documented here — it appears only in the finale/validate response, as an end-of-journey reward. Instructors deploying PerfShop in-house can point this property to their own games hub or to the official hosting depending on their context.
Response — 200 OK (failure)¶
No message, no indication of the reason for failure. The student simply has to retry.
Error codes¶
| Code | Cause |
|---|---|
| 400 | Unknown theme (key student.pedagogique.error.theme_unknown) |
| 401 | X-Student-Token header missing or invalid |
The dynamic agent code¶
Each student receives a unique agent code computed at /join from their UUID token. This code serves two purposes:
- Computing the dynamic answer for enigmas marked
DYNAMICin the catalog - Narrative identification in the BAC5 enigmas (secret agents)
Code format by level¶
| Level | Format | Extraction from the token |
|---|---|---|
| bac1, bac2 | 4 decimal digits | All digits of the UUID token, first 4 retained |
| bac3, bac4, bac5 | 4 uppercase hex characters | First 4 characters of the token after removing dashes, uppercased |
The format choice is pedagogical: BAC1/BAC2 students do not necessarily master hexadecimal, so their code stays decimal. From BAC3 onwards, hex manipulation is part of the expected skills.
Computing the dynamic answer per level¶
From the agent code, the answer to a DYNAMIC enigma is computed by DefaultPedagogiqueSessionService.computeDynamicAnswer() according to the level algorithm:
| Level | Algorithm | Example (code → answer) |
|---|---|---|
| bac1 | Sum of the 4 digits × 10 | 3847 → (3+8+4+7) × 10 = 220 |
| bac2 | Decimal code ÷ sum of the 4 digits (integer division) | 3847 → 3847 / 22 = 174 |
| bac3 | Sum of the decimal values of the 4 hex digits | A3F7 → 10 + 3 + 15 + 7 = 35 |
| bac4 | Decimal value of the hex code, mod 256 | A3F7 → 41975 mod 256 = 55 |
| bac5 | XOR of the high byte and the low byte | A3F7 → 0xA3 ^ 0xF7 = 84 |
The plain-text answer is then hashed with SHA-256 and stored in session.extractionAnswerHash. It is this hash that is compared to the answerHash received during POST /validate for dynamic enigmas.
Stars rule¶
The number of stars earned on completing the journey depends on the level and on respecting the allotted time:
| Level | Stars on time | Stars late |
|---|---|---|
| bac1 | 1 ⭐ | 1 ⭐ (guaranteed minimum) |
| bac2 | 2 ⭐ | 1 ⭐ |
| bac3 | 3 ⭐ | 2 ⭐ |
| bac4 | 4 ⭐ | 3 ⭐ |
| bac5 | 5 ⭐ | 4 ⭐ |
The inTime evaluation compares (completedAt - joinedAt) in seconds to timerTotal. A student who finishes at the last second is considered "on time".
curl example — full BAC3 journey¶
# === Instructor ===
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. Activate the BAC3 journey with memory cache (for heap dump demo)
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
# === Student ===
# 2. Join the journey — retrieves the 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 "My token: $STUDENT"
# 3. Compute the hash client-side then validate step 1
# Bash example with openssl — trim+lower+nospace normalization
ANSWER="my_answer"
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. (… validate the other steps one by one …)
# 5. Check the success page (shareable link)
curl "http://localhost:9080/api/chaos/student/pedagogique/succes/$STUDENT"
# === Instructor: check live sessions ===
curl -H "X-Admin-Token: $TOKEN_ADMIN" \
http://localhost:9080/api/chaos/student/pedagogique/sessions
# === Instructor: stop ===
curl -H "X-Admin-Token: $TOKEN_ADMIN" -X POST \
http://localhost:9080/api/chaos/student/pedagogique/deactivate
Points of attention¶
DB persistence vs memory cache
In memoryCache: true mode, sessions are written both to the cache and to the DB. In case of a backend crash, the cache is lost but sessions can be rebuilt from the pedagogique_sessions table. A restart does not invalidate student tokens — this is intentional to avoid progression loss.
Enigma replayability
Catalog enigmas are fixed per level — all students see the same questions in the same order. What varies per student is:
- The agent code and the dynamic answer for
DYNAMICenigmas - The draw of the 5 logic questions from 25 (BAC5 only)
This targeted personalization prevents direct answer sharing between neighboring students while keeping an identical journey for progression and evaluation.
Anti-cheat protection
Three measures prevent cheating:
- Answers are never exposed — neither in clear nor as hashes, by any endpoint
- Hashes are computed client-side — the student learns nothing by listening to network traffic
- The agent code is unique — even if a student shares their dynamic answer, it is only valid for their own code
Related links¶
chaos-student.md— self-service control (same controller)orders.md— injection of theagentCodein the order responsechaos-admin.md— instructor chaos control- Enigma catalog — full BAC1–BAC5 list
- Dynamic agent code — formula details