<answer> does not see an <award> that sits inside a composite. Instead of expanding the composite and finding the award, its sugar treats the composite as answer content and wraps it in an implicit award — so the document ends up with two awards and the authored one never grades.
Reproduction
<answer name="a"><group><award><math>1</math></award></group></answer>
Submit 1. a.creditAchieved is 0.
Measured, submitting 1 in each case:
| source |
awards created |
creditAchieved |
<answer><award><math>1</math></award></answer> |
1 |
1 ✅ |
<answer><group><award><math>1</math></award></group></answer> |
2 |
0 ❌ |
<answer><select numToSelect="1"><option><award><math>1</math></award></option></select></answer> |
2 |
0 ❌ |
<answer><repeat for="1 2" valueName="v"><award>$v</award></repeat></answer> |
3 |
0 ❌ |
<answer><conditionalContent condition="true"><award><math>1</math></award></conditionalContent></answer> |
2 |
0 ❌ |
<setup> is a milder case: <answer><setup>…</setup><award><math>1</math></award></answer> still grades correctly (credit 1), but it too creates a spurious second award, because the sugar wraps the <setup> as if it were answer content.
Cause
Answer.js's sugar walks matchedChildren and classifies each one with componentIsSpecifiedType(child, "award") (and friends). A composite child is not an award by component type, so it falls through to the branch that wraps leftover content in an implicit award. The composite is never expanded before that classification runs, so nothing inside it is ever considered.
This is the same class of defect as #1873 (<matrix> silently dropping composite children), but a different mechanism — <matrix> uses keepChildrenSerialized, <answer> classifies raw matchedChildren in sugar.
Why this matters now
<conditionalContent> has carried allowInSchemaAnywhere for a while, so the editor has always accepted the last row of that table while core quietly awarded no credit.
#1872 gives the same mark to <group>, <repeat>, <repeatForSequence>, <select>, <module>, <collect>, <shuffle>, <sort> and <setup>, on the grounds that these composites expand to copies of their content and so belong wherever that content belongs. That holds for every other typed container checked — <math>, <numberList>, <point>, <award>, <choiceInput>, <piecewiseFunction>, <tabular>, <spreadsheet>, <legend>, <triggerSet> and <odeSystem> all expand them correctly — but it does extend the existing silent failure in <answer> to eight more composites.
Generating awards with <repeat> (one award per acceptable value) is a natural thing to want to write, so the fix is presumably to expand composites before <answer>'s sugar classifies children, rather than to special-case the schema back.
Notes
The silent part is the worst of it. There is no schema diagnostic (getSchemaViolations() returns [] for all of the failing rows above) and no error in the document — the answer simply never scores.
🤖 Generated with Claude Code
<answer>does not see an<award>that sits inside a composite. Instead of expanding the composite and finding the award, its sugar treats the composite as answer content and wraps it in an implicit award — so the document ends up with two awards and the authored one never grades.Reproduction
Submit
1.a.creditAchievedis0.Measured, submitting
1in each case:creditAchieved<answer><award><math>1</math></award></answer><answer><group><award><math>1</math></award></group></answer><answer><select numToSelect="1"><option><award><math>1</math></award></option></select></answer><answer><repeat for="1 2" valueName="v"><award>$v</award></repeat></answer><answer><conditionalContent condition="true"><award><math>1</math></award></conditionalContent></answer><setup>is a milder case:<answer><setup>…</setup><award><math>1</math></award></answer>still grades correctly (credit 1), but it too creates a spurious second award, because the sugar wraps the<setup>as if it were answer content.Cause
Answer.js's sugar walksmatchedChildrenand classifies each one withcomponentIsSpecifiedType(child, "award")(and friends). A composite child is not anawardby component type, so it falls through to the branch that wraps leftover content in an implicit award. The composite is never expanded before that classification runs, so nothing inside it is ever considered.This is the same class of defect as #1873 (
<matrix>silently dropping composite children), but a different mechanism —<matrix>useskeepChildrenSerialized,<answer>classifies rawmatchedChildrenin sugar.Why this matters now
<conditionalContent>has carriedallowInSchemaAnywherefor a while, so the editor has always accepted the last row of that table while core quietly awarded no credit.#1872 gives the same mark to
<group>,<repeat>,<repeatForSequence>,<select>,<module>,<collect>,<shuffle>,<sort>and<setup>, on the grounds that these composites expand to copies of their content and so belong wherever that content belongs. That holds for every other typed container checked —<math>,<numberList>,<point>,<award>,<choiceInput>,<piecewiseFunction>,<tabular>,<spreadsheet>,<legend>,<triggerSet>and<odeSystem>all expand them correctly — but it does extend the existing silent failure in<answer>to eight more composites.Generating awards with
<repeat>(one award per acceptable value) is a natural thing to want to write, so the fix is presumably to expand composites before<answer>'s sugar classifies children, rather than to special-case the schema back.Notes
The silent part is the worst of it. There is no schema diagnostic (
getSchemaViolations()returns[]for all of the failing rows above) and no error in the document — the answer simply never scores.🤖 Generated with Claude Code