Skip to content

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:

  1. The agent code — each student receives their own code derived from their UUID, used to compute the answer to certain "dynamic" enigmas.
  2. 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

{
  "level": "bac3",
  "timerSeconds": 2400,
  "memoryCache": true
}
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

{
  "success": true,
  "level": "bac3",
  "totalSteps": 8,
  "timer": 2400,
  "memoryCacheEnabled": true
}
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)
  • MutableSession objects 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

{
  "success": true,
  "message": "Pedagogical session deactivated"
}

Message localized via student.pedagogique.deactivated.

Side effects

  1. If sessions exist, buildSessionSnapshot() is called and replaces the content of lastSessionSummary
  2. pedagogiqueActive = false, level reset to null, timer reset to 0
  3. hintsEnabled reset to true (default value for the next journey)
  4. memoryCache reset to false (the next journey will need to re-enable it explicitly if needed)
  5. pedagogiqueSessionService.clearAll() — all rows in the pedagogique_sessions table 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

{ "enabled": false }
Field Type Required Description
enabled boolean yes true = hints visible, false = button hidden

Response — 200 OK

{
  "success": true,
  "hintsEnabled": false
}

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:

{ "alias": "Alice" }
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:

  1. Agent code — 4 decimal digits (bac1/bac2) or 4 uppercase hex characters (bac3/bac4/bac5) extracted from the token
  2. Dynamic answer — computed per level (see The dynamic agent code below) then hashed with SHA-256 (extractionAnswerHash)
  3. Logic indices — 5 distinct indices drawn from [0..24] via an LCG seeded by the token (identical to the frontend implementation)
  4. 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

{
  "step": 3,
  "answerHash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
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)

{
  "valid": true,
  "attempts": 2,
  "completed": true,
  "inTime": true,
  "stars": 3,
  "maxStars": 3
}

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)

{
  "valid": false,
  "attempts": 2,
  "message": "Incorrect answer — try again!"
}

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

{ "level": 3 }
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:

{
  "success": true,
  "level": 0,
  "active": false
}

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 student
  • POST /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

{
  "results": [true, false, true, true, false]
}

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

{
  "theme": "performance",
  "answerHash": "hash_sha256_of_the_answer"
}
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)

{
  "valid": true,
  "gameUrl": "https://..."
}

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)

{
  "valid": false
}

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:

  1. Computing the dynamic answer for enigmas marked DYNAMIC in the catalog
  2. 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) 38473847 / 22 = 174
bac3 Sum of the decimal values of the 4 hex digits A3F710 + 3 + 15 + 7 = 35
bac4 Decimal value of the hex code, mod 256 A3F741975 mod 256 = 55
bac5 XOR of the high byte and the low byte A3F70xA3 ^ 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:

stars = level (1 to 5)
if elapsed time > timer → stars -= 1
minimum stars = 1
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:

  1. The agent code and the dynamic answer for DYNAMIC enigmas
  2. 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:

  1. Answers are never exposed — neither in clear nor as hashes, by any endpoint
  2. Hashes are computed client-side — the student learns nothing by listening to network traffic
  3. The agent code is unique — even if a student shares their dynamic answer, it is only valid for their own code