Summary
POST /api/skill-progression/record never validates prerequisites and never enforces difficulty monotonicity. A client can claim Advanced for React without any JavaScript baseline in a single request, or silently downgrade an existing Expert skill back to Beginner. The validation endpoint (/validate) exists but record does not call it — so #814 ("Skill difficulty progression not validated") remains unfixed in practice.
Evidence
src/routes/main_routes.py:578-626 (record_skill_completion route) validates only:
- that
difficulty maps to a known SkillDifficulty enum name (SkillDifficulty[difficulty.upper()], line 597), and
- that
assessment_score is a number in 0-100 (lines 601-611).
It never calls can_learn_skill / validate_skill_progression before recording.
src/utils/skill_progression.py:160-207 (record_skill_completion):
if skill_name not in self.user_skills[user_id]:
self.user_skills[user_id][skill_name] = {"difficulty": None, ...}
skill_data = self.user_skills[user_id][skill_name]
skill_data["difficulty"] = difficulty # line 196 — unconditional overwrite, no monotonic check
SKILL_PREREQUISITES (lines 41-112) defines real dependency rules (e.g. React ADVANCED requires JavaScript ADVANCED), and can_learn_skill (lines 121-158) enforces them — but nothing in the write path consults it. The get_recommended_next_skill helper (lines 213-241) even computes the next step from current_difficulty.value + 1, implying levels should move one at a time.
Impact
- Users can claim the highest tier of any skill instantly, bypassing the entire prerequisite graph.
- Re-recording a skill at a lower difficulty silently destroys the higher stored level (no
max() / monotonic check), so progress can be trivially wiped or corrupted.
Suggested Fix
Before storing, route record through can_learn_skill(user_id, skill_name, difficulty) (return 400 with the message when not allowed), and preserve the higher existing difficulty when a lower one is submitted (e.g. skill_data["difficulty"] = max(skill_data["difficulty"] or difficulty, difficulty)). Add route-level tests for prereq violation and downgrade.
Summary
POST /api/skill-progression/recordnever validates prerequisites and never enforces difficulty monotonicity. A client can claimAdvancedforReactwithout anyJavaScriptbaseline in a single request, or silently downgrade an existingExpertskill back toBeginner. The validation endpoint (/validate) exists butrecorddoes not call it — so #814 ("Skill difficulty progression not validated") remains unfixed in practice.Evidence
src/routes/main_routes.py:578-626(record_skill_completionroute) validates only:difficultymaps to a knownSkillDifficultyenum name (SkillDifficulty[difficulty.upper()], line 597), andassessment_scoreis a number in 0-100 (lines 601-611).It never calls
can_learn_skill/validate_skill_progressionbefore recording.src/utils/skill_progression.py:160-207(record_skill_completion):SKILL_PREREQUISITES(lines 41-112) defines real dependency rules (e.g.React ADVANCEDrequiresJavaScript ADVANCED), andcan_learn_skill(lines 121-158) enforces them — but nothing in the write path consults it. Theget_recommended_next_skillhelper (lines 213-241) even computes the next step fromcurrent_difficulty.value + 1, implying levels should move one at a time.Impact
max()/ monotonic check), so progress can be trivially wiped or corrupted.Suggested Fix
Before storing, route
recordthroughcan_learn_skill(user_id, skill_name, difficulty)(return 400 with the message whennot allowed), and preserve the higher existing difficulty when a lower one is submitted (e.g.skill_data["difficulty"] = max(skill_data["difficulty"] or difficulty, difficulty)). Add route-level tests for prereq violation and downgrade.