diff --git a/docs/COURSE_VERIFICATION.md b/docs/COURSE_VERIFICATION.md index 868881c..6eb39db 100644 --- a/docs/COURSE_VERIFICATION.md +++ b/docs/COURSE_VERIFICATION.md @@ -112,3 +112,36 @@ Remaining minor deviations (not requested to fix): `trackingPassed="80%"`, `trackingMode="full"`, per-quiz `trackingWeight` 1×7 + 21). Backups in `course_backup/`. The course now fulfills the specification in all major respects. + +## LMS completion fix (post-0.0.3) + +**Problem reported on LMS **: the 0.0.3 release reported +**0% completion** to the LMS even though the learner finished every quiz. +A HAR capture showed `cmi.core.score.raw` was sent (75, then 100) but +`cmi.core.lesson_status` was never reported as `passed`/`completed`/`failed`, +and `cmi.core.suspend_data.completedPages` was `[false, true, true, …]` — +index 0 (the first content page, "Welcome") was never marked complete. + +**Root cause**: a Xerte Nottingham off-by-one between `toCompletePages` +(built from `currActPage` over the 43 content nodes, before the built-in +Table-of-Contents menu page is inserted as `x_pages[0]`) and `page_nr` (used +by `exitInteraction`, which is `currActPage + 1` for every content page +because the menu takes `page_nr == 0`). `completedPages[0]` could only be set +by exiting the menu (which never happens), so `getSuccessStatus()` always +returned `'incomplete'`. Full analysis in `PROJECT_CONTEXT.md` § "SCORM +completion bug fix". + +**Fix applied**: `unmarkForCompletion="true"` on the Welcome page +(`linkID="PG1781882348053"`) in `source/data.xml` + `source/preview.xml`, +shifting `toCompletePages` to `[1,2,…,42]` so it aligns with the content +pages' `page_nr`. Verified locally with a SCORM 1.2 mock API harness: a +completed run now sends `cmi.core.lesson_status="passed"` (+ `exit`, +`score.raw`, `score.min`, `score.max`, `session_time`). + +**Pending (convention 9 — MANDATORY before merge)**: push the updated +`source/data.xml` + `source/preview.xml` into a running XOT instance, +confirm `play.php` returns 200, export a SCORM package, and grep the exported +`template.xml` to confirm `unmarkForCompletion="true"` is present on the +Welcome page and the question count / `trackingWeight` / `trackingPassed` / +empty-option count are unchanged. Then re-test on LMS that a +completed attempt reports `lesson_status=passed` and a non-zero grade. diff --git a/docs/PROJECT_CONTEXT.md b/docs/PROJECT_CONTEXT.md index 0ba96b6..976ff97 100644 --- a/docs/PROJECT_CONTEXT.md +++ b/docs/PROJECT_CONTEXT.md @@ -67,6 +67,8 @@ with agents and building agents. score reporting verified (`apiwrapper_1.2.js` + `xttracking_scorm1.2.js` → `cmi.core.score.raw` + `lesson_status`). - **No Trial MCQ** (an early trial page was removed). +- **Welcome page carries `unmarkForCompletion="true"`** — see "SCORM completion + bug fix" below for why; it is the fix for LMS completion tracking. - `play.php` returns 200; the export is `Secure_code_development_scorm.zip`. ## Conventions (MANDATORY for any content changes) @@ -138,6 +140,57 @@ These were agreed with the course owner and must be preserved: rich HTML renders correctly, and `delaySecs="0"` disables the timed reveal). Plain Text would be "purer" but converting 27 pages is not worth the risk. +## SCORM completion bug fix (0.0.4 — `unmarkForCompletion` on Welcome) + +**Symptom**: on LMS the course reported **0% completion** +even though the learner finished every quiz and `cmi.core.score.raw` was +being sent (75, then 100). `cmi.core.lesson_status` was never reported as +`passed`/`completed`/`failed`. + +**Root cause** (a Xerte Nottingham engine bug, worked around at the content +level): with `navigation="Menu with Page Controls"`, Xerte inserts a built-in +Table-of-Contents page as `x_pages[0]` **after** `toCompletePages` is built +from `currActPage` over the 43 content nodes. So every content page's +`page_nr` (used by `exitInteraction` to mark `completedPages[i]`) is +`currActPage + 1` — a systematic **off-by-one**: + +- `toCompletePages[0] == 0` is only matched by `page_nr == 0` (the menu), + which is never exited (menu pages are excluded from `x_endPageTracking`), + so `completedPages[0]` (the first content page, "Welcome") stayed `false` + forever. +- The last content page (Final quiz, `page_nr == 43`) had no + `toCompletePages` entry, so its completion was lost too. +- `getSuccessStatus()` returns `'incomplete'` whenever any `completedPages[i]` + is `false`, so `finishTracking()` set `cmi.core.lesson_status='incomplete'` + (a no-op against Moodle's pre-set 'incomplete'), and the LMS never left the + incomplete state. + +**Fix**: add `unmarkForCompletion="true"` to the **first content page** +("Welcome & how to use this course", `linkID="PG1781882348053"`) in +`source/data.xml` + `source/preview.xml`. This makes `markedPages` skip +`currActPage == 0`, so `toCompletePages` becomes `[1,2,…,42]`, which now +matches the content pages' `page_nr` values (`1…42`). `completedPages[0]` +(Welcome) is now marked `true` when the learner leaves it, and when all +tracked pages are visited `getSuccessStatus()` returns `passed`/`failed` +against `trackingPassed="80%"`. Verified locally with a SCORM 1.2 mock API: +a completed run sends `cmi.core.lesson_status="passed"`, `cmi.core.exit=""`, +`cmi.core.score.raw`, `score.min`, `score.max`, and `cmi.core.session_time`. + +**Trade-off**: the Final quiz (`page_nr == 43`) is still not in +`toCompletePages` (there is no index 43), so its *completion* is not tracked +in `completedPages` — but its *score* is still tracked via its interactions +and `trackingWeight="21"`, so the LMS grade and pass/fail are correct. The +Welcome page is no longer "required" for completion, which is acceptable for +an intro page. A proper fix would be in the Xerte engine (`xenith.js`: build +`toCompletePages` from `page_nr` after the menu is inserted, or do not insert +the menu into `x_pages` for tracking); this content workaround is used because +the release workflow builds from the XOT engine and an engine patch would not +persist across XOT rebuilds. + +**Do not remove `unmarkForCompletion="true"` from the Welcome page** without +a replacement fix — re-introducing the off-by-one will silently break LMS +completion reporting again. + ## SCORM scoring (how the LMS grade is computed) SCORM 1.2 reports **one** `cmi.core.score.raw` (0–100) and **one** diff --git a/source/data.xml b/source/data.xml index 8a76d83..240f353 100644 --- a/source/data.xml +++ b/source/data.xml @@ -1,5 +1,5 @@ -Welcome to Secure code development +Welcome to Secure code development

This course is for engineers building software with agents and for engineers building agents. Whether you are designing agentic systems, integrating LLMs, or writing the services that agents call, secure code development matters.

diff --git a/source/preview.xml b/source/preview.xml index 8a76d83..240f353 100644 --- a/source/preview.xml +++ b/source/preview.xml @@ -1,5 +1,5 @@ -Welcome to Secure code development +Welcome to Secure code development

This course is for engineers building software with agents and for engineers building agents. Whether you are designing agentic systems, integrating LLMs, or writing the services that agents call, secure code development matters.