Problem
POST /attempt trusts the client to supply phonics_tags, theme, and difficulty for an arbitrary word. Those values directly update struggle counts, theme preferences, and adaptive difficulty. A stale, buggy, or malicious client can therefore poison a student's learning model with metadata that disagrees with data/word_bank.json. The hint and story endpoints also accept effectively unbounded/arbitrary word content; an empty hint word can reach word[0]/word[-1] and fail at runtime.
Curriculum facts should have one server-owned source of truth.
Proposed change
- Change attempt input to the minimum event data:
student_id, a canonical word ID/value, outcome, and elapsed time.
- Resolve difficulty, phonics, and theme from the server-side word bank before updating a profile.
- Reject unknown/inactive words with a stable
404/422 error; normalize casing consistently.
- Add bounded Pydantic constraints for all word lists and strings used by hints/stories (item count, item length, non-empty values, total payload size).
- Decide and document how word-bank edits affect historical attempts; snapshot curriculum metadata/version on each event if historical reproducibility is required.
- Preserve backward compatibility through a versioned endpoint or deprecation window rather than silently continuing to trust duplicate client metadata.
Acceptance criteria
- Clients cannot choose or override the phonics, theme, or difficulty recorded for an attempt.
- Unknown words, empty words, oversized strings, and oversized story lists produce deterministic 4xx responses, never 500s.
- Mixed-case input resolves to one documented canonical form.
- Profile/recommendation tests prove that stored learning signals match the word-bank record.
- API documentation and WordBloc integration examples reflect the new contract and migration path.
Relevant code
api/routes.py (AttemptRequest, HintRequest, StoryRequest)
agent/profiler.py (record_attempt)
agent/word_bank.py
agent/hint_generator.py
agent/story_mode.py
Problem
POST /attempttrusts the client to supplyphonics_tags,theme, anddifficultyfor an arbitraryword. Those values directly update struggle counts, theme preferences, and adaptive difficulty. A stale, buggy, or malicious client can therefore poison a student's learning model with metadata that disagrees withdata/word_bank.json. The hint and story endpoints also accept effectively unbounded/arbitrary word content; an empty hint word can reachword[0]/word[-1]and fail at runtime.Curriculum facts should have one server-owned source of truth.
Proposed change
student_id, a canonical word ID/value, outcome, and elapsed time.404/422error; normalize casing consistently.Acceptance criteria
Relevant code
api/routes.py(AttemptRequest,HintRequest,StoryRequest)agent/profiler.py(record_attempt)agent/word_bank.pyagent/hint_generator.pyagent/story_mode.py