Skip to content

Commit 683131b

Browse files
RichardoCRobot
andauthored
fix(scorm): mark Welcome page unmarkForCompletion so LMS completion reports (#5)
The 0.0.3 release reported 0% completion to an LMS even though the learner finished every quiz. Root cause, traced through the HAR of a real attempt plus Xerte's xttracking_scorm1.2.js + xenith.js: 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 could only be 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. - 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. cmi.core.score.raw WAS being sent (75, then 100) but never mattered to the displayed completion. Changes to source/data.xml + source/preview.xml (identical): - unmarkForCompletion="true" on the Welcome page (linkID="PG1781882348053"). This makes markedPages skip currActPage == 0, so toCompletePages becomes [1,2,...,42], aligning with the content pages' page_nr values. completedPages[0] (Welcome) is now marked true when the learner leaves it. Validated end-to-end through a running XOT (docker-container branch) instance on host port 8088: - Pushed source/data.xml + source/preview.xml into the container; both carry unmarkForCompletion="true" on the Welcome page and are byte-identical to the repo source. - play.php?template_id=1 returns 200; HTML5 editor opens. - SCORM export is valid SCORM 1.2 (one SCO, 13 MB) and the exported template.xml carries unmarkForCompletion="true", trackingMode="full", trackingWeight 1x7 + 21, trackingPassed="80%", 45 questions, 8 quizzes, 0 empty options -- all unchanged from 0.0.3 except the fix. - Drove the exported package against a SCORM 1.2 mock API: a completed run sends cmi.core.lesson_status="passed" (score.raw=100), a failing run sends lesson_status="failed" (score.raw=62.5), and an incomplete run sends lesson_status="incomplete" + cmi.core.exit="suspend". Before the fix the completed run reported "incomplete". Trade-off: the Final quiz (page_nr == 43) is still outside toCompletePages (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), but that would not survive XOT rebuilds; this content-level workaround is used because the release workflow builds from the XOT engine. 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. Guidance updates: - docs/PROJECT_CONTEXT.md: added "SCORM completion bug fix" section documenting the root cause, fix, and trade-off; noted the Welcome page carries unmarkForCompletion="true" in the current-state list. - docs/COURSE_VERIFICATION.md: added "LMS completion fix (post-0.0.3)" section with the HAR evidence, root cause, fix, and validation results. Generated with pi 0.80.3 and GLM 5.2 Co-authored-by: Robot <robosan@Robots-Virtual-Machine.local>
1 parent b6ad376 commit 683131b

4 files changed

Lines changed: 88 additions & 2 deletions

File tree

docs/COURSE_VERIFICATION.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,36 @@ Remaining minor deviations (not requested to fix):
112112
`trackingPassed="80%"`, `trackingMode="full"`, per-quiz `trackingWeight`
113113
1×7 + 21). Backups in `course_backup/`. The course now fulfills
114114
the specification in all major respects.
115+
116+
## LMS completion fix (post-0.0.3)
117+
118+
**Problem reported on LMS **: the 0.0.3 release reported
119+
**0% completion** to the LMS even though the learner finished every quiz.
120+
A HAR capture showed `cmi.core.score.raw` was sent (75, then 100) but
121+
`cmi.core.lesson_status` was never reported as `passed`/`completed`/`failed`,
122+
and `cmi.core.suspend_data.completedPages` was `[false, true, true, …]`
123+
index 0 (the first content page, "Welcome") was never marked complete.
124+
125+
**Root cause**: a Xerte Nottingham off-by-one between `toCompletePages`
126+
(built from `currActPage` over the 43 content nodes, before the built-in
127+
Table-of-Contents menu page is inserted as `x_pages[0]`) and `page_nr` (used
128+
by `exitInteraction`, which is `currActPage + 1` for every content page
129+
because the menu takes `page_nr == 0`). `completedPages[0]` could only be set
130+
by exiting the menu (which never happens), so `getSuccessStatus()` always
131+
returned `'incomplete'`. Full analysis in `PROJECT_CONTEXT.md` § "SCORM
132+
completion bug fix".
133+
134+
**Fix applied**: `unmarkForCompletion="true"` on the Welcome page
135+
(`linkID="PG1781882348053"`) in `source/data.xml` + `source/preview.xml`,
136+
shifting `toCompletePages` to `[1,2,…,42]` so it aligns with the content
137+
pages' `page_nr`. Verified locally with a SCORM 1.2 mock API harness: a
138+
completed run now sends `cmi.core.lesson_status="passed"` (+ `exit`,
139+
`score.raw`, `score.min`, `score.max`, `session_time`).
140+
141+
**Pending (convention 9 — MANDATORY before merge)**: push the updated
142+
`source/data.xml` + `source/preview.xml` into a running XOT instance,
143+
confirm `play.php` returns 200, export a SCORM package, and grep the exported
144+
`template.xml` to confirm `unmarkForCompletion="true"` is present on the
145+
Welcome page and the question count / `trackingWeight` / `trackingPassed` /
146+
empty-option count are unchanged. Then re-test on LMS that a
147+
completed attempt reports `lesson_status=passed` and a non-zero grade.

docs/PROJECT_CONTEXT.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ with agents and building agents.
6767
score reporting verified (`apiwrapper_1.2.js` + `xttracking_scorm1.2.js`
6868
`cmi.core.score.raw` + `lesson_status`).
6969
- **No Trial MCQ** (an early trial page was removed).
70+
- **Welcome page carries `unmarkForCompletion="true"`** — see "SCORM completion
71+
bug fix" below for why; it is the fix for LMS completion tracking.
7072
- `play.php` returns 200; the export is `Secure_code_development_scorm.zip`.
7173

7274
## Conventions (MANDATORY for any content changes)
@@ -138,6 +140,57 @@ These were agreed with the course owner and must be preserved:
138140
rich HTML renders correctly, and `delaySecs="0"` disables the timed reveal).
139141
Plain Text would be "purer" but converting 27 pages is not worth the risk.
140142

143+
## SCORM completion bug fix (0.0.4 — `unmarkForCompletion` on Welcome)
144+
145+
**Symptom**: on LMS the course reported **0% completion**
146+
even though the learner finished every quiz and `cmi.core.score.raw` was
147+
being sent (75, then 100). `cmi.core.lesson_status` was never reported as
148+
`passed`/`completed`/`failed`.
149+
150+
**Root cause** (a Xerte Nottingham engine bug, worked around at the content
151+
level): with `navigation="Menu with Page Controls"`, Xerte inserts a built-in
152+
Table-of-Contents page as `x_pages[0]` **after** `toCompletePages` is built
153+
from `currActPage` over the 43 content nodes. So every content page's
154+
`page_nr` (used by `exitInteraction` to mark `completedPages[i]`) is
155+
`currActPage + 1` — a systematic **off-by-one**:
156+
157+
- `toCompletePages[0] == 0` is only matched by `page_nr == 0` (the menu),
158+
which is never exited (menu pages are excluded from `x_endPageTracking`),
159+
so `completedPages[0]` (the first content page, "Welcome") stayed `false`
160+
forever.
161+
- The last content page (Final quiz, `page_nr == 43`) had no
162+
`toCompletePages` entry, so its completion was lost too.
163+
- `getSuccessStatus()` returns `'incomplete'` whenever any `completedPages[i]`
164+
is `false`, so `finishTracking()` set `cmi.core.lesson_status='incomplete'`
165+
(a no-op against Moodle's pre-set 'incomplete'), and the LMS never left the
166+
incomplete state.
167+
168+
**Fix**: add `unmarkForCompletion="true"` to the **first content page**
169+
("Welcome & how to use this course", `linkID="PG1781882348053"`) in
170+
`source/data.xml` + `source/preview.xml`. This makes `markedPages` skip
171+
`currActPage == 0`, so `toCompletePages` becomes `[1,2,…,42]`, which now
172+
matches the content pages' `page_nr` values (`1…42`). `completedPages[0]`
173+
(Welcome) is now marked `true` when the learner leaves it, and when all
174+
tracked pages are visited `getSuccessStatus()` returns `passed`/`failed`
175+
against `trackingPassed="80%"`. Verified locally with a SCORM 1.2 mock API:
176+
a completed run sends `cmi.core.lesson_status="passed"`, `cmi.core.exit=""`,
177+
`cmi.core.score.raw`, `score.min`, `score.max`, and `cmi.core.session_time`.
178+
179+
**Trade-off**: the Final quiz (`page_nr == 43`) is still not in
180+
`toCompletePages` (there is no index 43), so its *completion* is not tracked
181+
in `completedPages` — but its *score* is still tracked via its interactions
182+
and `trackingWeight="21"`, so the LMS grade and pass/fail are correct. The
183+
Welcome page is no longer "required" for completion, which is acceptable for
184+
an intro page. A proper fix would be in the Xerte engine (`xenith.js`: build
185+
`toCompletePages` from `page_nr` after the menu is inserted, or do not insert
186+
the menu into `x_pages` for tracking); this content workaround is used because
187+
the release workflow builds from the XOT engine and an engine patch would not
188+
persist across XOT rebuilds.
189+
190+
**Do not remove `unmarkForCompletion="true"` from the Welcome page** without
191+
a replacement fix — re-introducing the off-by-one will silently break LMS
192+
completion reporting again.
193+
141194
## SCORM scoring (how the LMS grade is computed)
142195

143196
SCORM 1.2 reports **one** `cmi.core.score.raw` (0–100) and **one**

source/data.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0"?>
2-
<learningObject editorVersion="3" targetFolder="Nottingham" name="Secure code development" language="en-GB" navigation="Menu with Page Controls" textSize="12" theme="default" themeIcons="false" displayMode="fill window" responsive="true" trackingMode="full" trackingPassed="80%" trackingPageTimeout="0" forceTrackingMode="false" restartOptions="ask" hideSaveSession="false" saveSessionLabel="Save Session" closeSessionLabel="Close Session" saveSessionIc="false" saveSessionIcon="fas fa-save"><text linkID="PG1781882348053" name="Welcome &amp;amp; how to use this course"><![CDATA[<h2>Welcome to Secure code development</h2>
2+
<learningObject editorVersion="3" targetFolder="Nottingham" name="Secure code development" language="en-GB" navigation="Menu with Page Controls" textSize="12" theme="default" themeIcons="false" displayMode="fill window" responsive="true" trackingMode="full" trackingPassed="80%" trackingPageTimeout="0" forceTrackingMode="false" restartOptions="ask" hideSaveSession="false" saveSessionLabel="Save Session" closeSessionLabel="Close Session" saveSessionIc="false" saveSessionIcon="fas fa-save"><text unmarkForCompletion="true" linkID="PG1781882348053" name="Welcome &amp;amp; how to use this course"><![CDATA[<h2>Welcome to Secure code development</h2>
33
44
<p>This course is for <strong>engineers building software with agents</strong> and for <strong>engineers building agents</strong>. Whether you are designing agentic systems, integrating LLMs, or writing the services that agents call, secure code development matters.</p>
55

source/preview.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0"?>
2-
<learningObject editorVersion="3" targetFolder="Nottingham" name="Secure code development" language="en-GB" navigation="Menu with Page Controls" textSize="12" theme="default" themeIcons="false" displayMode="fill window" responsive="true" trackingMode="full" trackingPassed="80%" trackingPageTimeout="0" forceTrackingMode="false" restartOptions="ask" hideSaveSession="false" saveSessionLabel="Save Session" closeSessionLabel="Close Session" saveSessionIc="false" saveSessionIcon="fas fa-save"><text linkID="PG1781882348053" name="Welcome &amp;amp; how to use this course"><![CDATA[<h2>Welcome to Secure code development</h2>
2+
<learningObject editorVersion="3" targetFolder="Nottingham" name="Secure code development" language="en-GB" navigation="Menu with Page Controls" textSize="12" theme="default" themeIcons="false" displayMode="fill window" responsive="true" trackingMode="full" trackingPassed="80%" trackingPageTimeout="0" forceTrackingMode="false" restartOptions="ask" hideSaveSession="false" saveSessionLabel="Save Session" closeSessionLabel="Close Session" saveSessionIc="false" saveSessionIcon="fas fa-save"><text unmarkForCompletion="true" linkID="PG1781882348053" name="Welcome &amp;amp; how to use this course"><![CDATA[<h2>Welcome to Secure code development</h2>
33
44
<p>This course is for <strong>engineers building software with agents</strong> and for <strong>engineers building agents</strong>. Whether you are designing agentic systems, integrating LLMs, or writing the services that agents call, secure code development matters.</p>
55

0 commit comments

Comments
 (0)