Summary
Build certification exams — timed, server-graded assessments drawn from a question bank that a learner takes at the end of a track, with a pass threshold, a retake cooldown, and a verifiable credential issued on passing. Quizzes today are practice-only: src/components/QuizEngine.tsx receives correctIndex in its props and grades entirely in the browser, which is fine for practice and unusable as the basis for a credential.
Background
LearnVault's whole premise is that learning is the proof of work — the credential has to mean something to the sponsor funding the scholarship and to the employer reading it. That requires an assessment whose answers the candidate cannot read out of the page they are sitting on.
This is the missing link between the existing course content and the existing credential machinery (server/src/controllers/certificates.controller.ts, the scholar_nft contract). Right now a learner can complete a track without ever demonstrating retained knowledge under any constraint.
Scope
Backend and frontend. Credential issuance reuses the existing certificate and ScholarNFT paths — no new contract work. Proctoring by webcam, identity verification, and browser lockdown are explicitly out of scope and should not be attempted here.
What to build
1. Question bank (database + admin)
- Migration for
exam_definitions, exam_questions, exam_attempts, and exam_answers. Questions carry difficulty, topic tags, and a correct-answer column that never leaves the server.
- Per-exam config: question count, time limit, pass percentage, retake cooldown, and how many questions to draw per topic.
- Admin CRUD for authoring exams and questions, following the existing
server/src/controllers/admin-courses.controller.ts patterns and reusing the admin middleware.
2. Exam engine (backend)
POST /api/exams/:id/start creates an attempt, draws a randomised question set with shuffled options, and returns questions stripped of answers. Server records the deadline.
POST /api/exams/attempts/:id/answer saves one answer at a time so a dropped connection loses one question, not the exam.
POST /api/exams/attempts/:id/submit grades server-side, enforces the deadline server-side, and returns a per-topic breakdown.
- Enforce the retake cooldown and reject attempts on a track the learner has not completed.
- An attempt that passes triggers the existing certificate issuance path.
3. Exam runner (frontend)
- A focused
/exams/:id page: one question at a time, a countdown clock, a question navigator with answered/unanswered state, and a review screen before submission.
- Answers persist as they are given, so a refresh resumes the same attempt with the correct remaining time rather than restarting or forfeiting it.
- A results screen with the per-topic breakdown, a pass/fail outcome, the link to the earned credential, and — on a fail — which topics to revisit and when the retake unlocks.
- Full keyboard operability, and a live-region announcement at the five-minute mark. A timed test that only signals visually excludes part of the audience.
4. i18n
- All new strings across
en, es, fr, sw, plus the regenerated pseudo-locale.
5. Tests
- The start endpoint response contains no correct answers — assert on the serialised payload, this is the one test that matters most.
- Grading is correct for single-answer, multi-answer, and unanswered questions.
- A submission after the deadline is rejected even if the client clock says otherwise.
- Two attempts draw different question sets from the same bank.
- Cooldown blocks an early retake; passing issues exactly one credential, and resubmitting the same attempt does not issue a second.
- Frontend: refresh mid-exam resumes the attempt with the correct time remaining.
Acceptance criteria
Notes for contributors
- Leave
QuizEngine.tsx alone. In-lesson practice quizzes are a different product with different requirements, and merging the two would either weaken the exam or make practice quizzes needlessly heavy.
- Never send the answer key to the client "and hide it in the UI". If it is in the network response, it is public.
- Treat a submitted attempt as immutable. Re-grading a stored attempt should be possible; mutating one should not be.
Summary
Build certification exams — timed, server-graded assessments drawn from a question bank that a learner takes at the end of a track, with a pass threshold, a retake cooldown, and a verifiable credential issued on passing. Quizzes today are practice-only:
src/components/QuizEngine.tsxreceivescorrectIndexin its props and grades entirely in the browser, which is fine for practice and unusable as the basis for a credential.Background
LearnVault's whole premise is that learning is the proof of work — the credential has to mean something to the sponsor funding the scholarship and to the employer reading it. That requires an assessment whose answers the candidate cannot read out of the page they are sitting on.
This is the missing link between the existing course content and the existing credential machinery (
server/src/controllers/certificates.controller.ts, thescholar_nftcontract). Right now a learner can complete a track without ever demonstrating retained knowledge under any constraint.Scope
Backend and frontend. Credential issuance reuses the existing certificate and ScholarNFT paths — no new contract work. Proctoring by webcam, identity verification, and browser lockdown are explicitly out of scope and should not be attempted here.
What to build
1. Question bank (database + admin)
exam_definitions,exam_questions,exam_attempts, andexam_answers. Questions carry difficulty, topic tags, and a correct-answer column that never leaves the server.server/src/controllers/admin-courses.controller.tspatterns and reusing the admin middleware.2. Exam engine (backend)
POST /api/exams/:id/startcreates an attempt, draws a randomised question set with shuffled options, and returns questions stripped of answers. Server records the deadline.POST /api/exams/attempts/:id/answersaves one answer at a time so a dropped connection loses one question, not the exam.POST /api/exams/attempts/:id/submitgrades server-side, enforces the deadline server-side, and returns a per-topic breakdown.3. Exam runner (frontend)
/exams/:idpage: one question at a time, a countdown clock, a question navigator with answered/unanswered state, and a review screen before submission.4. i18n
en,es,fr,sw, plus the regenerated pseudo-locale.5. Tests
Acceptance criteria
Notes for contributors
QuizEngine.tsxalone. In-lesson practice quizzes are a different product with different requirements, and merging the two would either weaken the exam or make practice quizzes needlessly heavy.