@conform-ed/contracts 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-format.log +6 -0
  3. package/.turbo/turbo-lint.log +4 -0
  4. package/.turbo/turbo-test.log +196 -0
  5. package/.turbo/turbo-typecheck.log +2 -0
  6. package/README.md +52 -0
  7. package/caliper-v1_2-zod-templates.md +123 -0
  8. package/case-v1_1-zod-templates.md +290 -0
  9. package/cat-v1_0-zod-templates.md +73 -0
  10. package/cc-v1_3-zod-templates.md +531 -0
  11. package/cc-v1_4-zod-templates.md +247 -0
  12. package/clr-v2_0-zod-templates.md +117 -0
  13. package/cmi5-v1_0-zod-templates.md +9 -0
  14. package/lti-zod-templates.md +11 -0
  15. package/oneroster-v1_2-zod-templates.md +76 -0
  16. package/open-badges-v3_0-zod-templates.md +66 -0
  17. package/package.json +42 -0
  18. package/qti-v2_1-zod-templates.md +45 -0
  19. package/qti-v2_2-zod-templates.md +32 -0
  20. package/qti-v3_0_1-zod-templates.md +421 -0
  21. package/src/adapter.ts +83 -0
  22. package/src/caliper/v1_2/caliper_v1p2_bootcamp_schema.ts +781 -0
  23. package/src/caliper/v1_2/index.ts +37 -0
  24. package/src/caliper/v1_2/shared.ts +122 -0
  25. package/src/caliper/v1_2/textual_requirements.ts +219 -0
  26. package/src/case/v1_1/case_v1p1_cfassociation_jsonschema1.ts +1 -0
  27. package/src/case/v1_1/case_v1p1_cfassociationgrouping_jsonschema1.ts +1 -0
  28. package/src/case/v1_1/case_v1p1_cfassociationset_jsonschema1.ts +1 -0
  29. package/src/case/v1_1/case_v1p1_cfconceptset_jsonschema1.ts +1 -0
  30. package/src/case/v1_1/case_v1p1_cfdocument_jsonschema1.ts +1 -0
  31. package/src/case/v1_1/case_v1p1_cfdocumentset_jsonschema1.ts +1 -0
  32. package/src/case/v1_1/case_v1p1_cfitem_jsonschema1.ts +1 -0
  33. package/src/case/v1_1/case_v1p1_cfitemtypeset_jsonschema1.ts +1 -0
  34. package/src/case/v1_1/case_v1p1_cflicense_jsonschema1.ts +1 -0
  35. package/src/case/v1_1/case_v1p1_cfpackage_jsonschema1.ts +1 -0
  36. package/src/case/v1_1/case_v1p1_cfrubric_jsonschema1.ts +1 -0
  37. package/src/case/v1_1/case_v1p1_cfsubjectset_jsonschema1.ts +1 -0
  38. package/src/case/v1_1/case_v1p1_imsx_statusinfo_jsonschema1.ts +1 -0
  39. package/src/case/v1_1/case_v1p1_openapi3_restbinding_schema.ts +113 -0
  40. package/src/case/v1_1/index.ts +95 -0
  41. package/src/case/v1_1/shared.ts +384 -0
  42. package/src/cat/v1_0/cat_v1p0_restbinding_operations_schema.ts +75 -0
  43. package/src/cat/v1_0/index.ts +124 -0
  44. package/src/cat/v1_0/shared.ts +345 -0
  45. package/src/clr/v2_0/clr_v2p0_achievementcredential_schema.ts +16 -0
  46. package/src/clr/v2_0/clr_v2p0_clrcredential_schema.ts +9 -0
  47. package/src/clr/v2_0/clr_v2p0_endorsementcredential_schema.ts +11 -0
  48. package/src/clr/v2_0/clr_v2p0_getclrcredentialsresponse_schema.ts +1 -0
  49. package/src/clr/v2_0/clr_v2p0_imsx_statusinfo_schema.ts +8 -0
  50. package/src/clr/v2_0/clr_v2p0_profile_schema.ts +9 -0
  51. package/src/clr/v2_0/index.ts +22 -0
  52. package/src/clr/v2_0/shared.ts +206 -0
  53. package/src/cmi5/index.ts +3 -0
  54. package/src/cmi5/v1_0/index.ts +134 -0
  55. package/src/common-cartridge/v1_3/ccv1p3_imsccauth_v1p3.ts +26 -0
  56. package/src/common-cartridge/v1_3/ccv1p3_imscp_v1p2_v1p0.ts +352 -0
  57. package/src/common-cartridge/v1_3/ccv1p3_imscsmd_v1p0.ts +35 -0
  58. package/src/common-cartridge/v1_3/ccv1p3_imsdt_v1p3.ts +33 -0
  59. package/src/common-cartridge/v1_3/ccv1p3_imswl_v1p3.ts +23 -0
  60. package/src/common-cartridge/v1_3/ccv1p3_lomccltilink_v1p0.ts +14 -0
  61. package/src/common-cartridge/v1_3/ccv1p3_lommanifest_v1p0.ts +14 -0
  62. package/src/common-cartridge/v1_3/ccv1p3_lomresource_v1p0.ts +14 -0
  63. package/src/common-cartridge/v1_3/ccv1p3_qtiasiv1p2p1_v1p0.ts +964 -0
  64. package/src/common-cartridge/v1_3/index.ts +41 -0
  65. package/src/common-cartridge/v1_3/lom-internal.ts +396 -0
  66. package/src/common-cartridge/v1_3/shared.ts +68 -0
  67. package/src/common-cartridge/v1_4/core/ccv1p4_imscp_v1p2_v1p0.ts +360 -0
  68. package/src/common-cartridge/v1_4/core/ccv1p4_lommanifest_v1p0.ts +14 -0
  69. package/src/common-cartridge/v1_4/core/ccv1p4_lomresource_v1p0.ts +14 -0
  70. package/src/common-cartridge/v1_4/extension/cc_extresource_assignmentv1p0_v1p0.ts +61 -0
  71. package/src/common-cartridge/v1_4/extension/ccv1p4_cpextensionv1p2_v1p0.ts +20 -0
  72. package/src/common-cartridge/v1_4/extension/ims_openvideov1p0_v1p0.ts +325 -0
  73. package/src/common-cartridge/v1_4/index.ts +104 -0
  74. package/src/common-cartridge/v1_4/k12/ccv1p4_imscp_v1p2_v1p0.ts +19 -0
  75. package/src/common-cartridge/v1_4/k12/ccv1p4_imscp_v1p2_v1p0_thin.ts +17 -0
  76. package/src/common-cartridge/v1_4/k12/ccv1p4_lommanifest_v1p0.ts +14 -0
  77. package/src/common-cartridge/v1_4/k12/ccv1p4_lomresource_v1p0.ts +14 -0
  78. package/src/common-cartridge/v1_4/lom-internal.ts +476 -0
  79. package/src/common-cartridge/v1_4/shared/ccv1p4_imsccauth_v1p4.ts +26 -0
  80. package/src/common-cartridge/v1_4/shared/ccv1p4_imscsmd_v1p1.ts +36 -0
  81. package/src/common-cartridge/v1_4/shared/ccv1p4_imsdt_v1p4.ts +33 -0
  82. package/src/common-cartridge/v1_4/shared/ccv1p4_imslticc_v1p4.ts +45 -0
  83. package/src/common-cartridge/v1_4/shared/ccv1p4_imswl_v1p4.ts +23 -0
  84. package/src/common-cartridge/v1_4/shared/ccv1p4_lomccltilink_v1p0.ts +14 -0
  85. package/src/common-cartridge/v1_4/shared/ccv1p4_qtiasiv1p2p1_v1p0.ts +964 -0
  86. package/src/common-cartridge/v1_4/shared/imsbasiclti_v1p0p1.ts +24 -0
  87. package/src/common-cartridge/v1_4/shared/imslticm_v1p0.ts +23 -0
  88. package/src/common-cartridge/v1_4/shared/imslticp_v1p0.ts +57 -0
  89. package/src/common-cartridge/v1_4/shared/lineitem_v1p0.ts +35 -0
  90. package/src/common-cartridge/v1_4/shared/resourcea11ymetadata-20210915.ts +110 -0
  91. package/src/common-cartridge/v1_4/shared.ts +68 -0
  92. package/src/common-cartridge/v1_4/thin/ccv1p4_imscp_v1p2_v1p0.ts +243 -0
  93. package/src/common-cartridge/v1_4/thin/ccv1p4_lommanifest_v1p0.ts +14 -0
  94. package/src/common-cartridge/v1_4/thin/ccv1p4_lomresource_v1p0.ts +14 -0
  95. package/src/common-cartridge/v1_4/vdex/imsmd_loose_v1p3p2.ts +13 -0
  96. package/src/common-cartridge/v1_4/vdex/imsvdex_v1p0.ts +124 -0
  97. package/src/config.ts +121 -0
  98. package/src/index.ts +32 -0
  99. package/src/lti/ags/v2_0/index.ts +97 -0
  100. package/src/lti/deep-linking/v2_0/index.ts +100 -0
  101. package/src/lti/index.ts +26 -0
  102. package/src/lti/nrps/v2_0/index.ts +69 -0
  103. package/src/lti/proctoring/v1_0/index.ts +131 -0
  104. package/src/lti/shared.ts +66 -0
  105. package/src/lti/v1_3/index.ts +84 -0
  106. package/src/oneroster/v1_2/index.ts +156 -0
  107. package/src/oneroster/v1_2/or_v1p2_csv_binding_schema.ts +427 -0
  108. package/src/oneroster/v1_2/or_v1p2_gradebook_restbinding_schema.ts +120 -0
  109. package/src/oneroster/v1_2/or_v1p2_gradebook_service_schema.ts +243 -0
  110. package/src/oneroster/v1_2/or_v1p2_resource_restbinding_schema.ts +24 -0
  111. package/src/oneroster/v1_2/or_v1p2_resource_service_schema.ts +47 -0
  112. package/src/oneroster/v1_2/or_v1p2_rostering_restbinding_schema.ts +84 -0
  113. package/src/oneroster/v1_2/or_v1p2_rostering_service_schema.ts +288 -0
  114. package/src/oneroster/v1_2/shared.ts +90 -0
  115. package/src/open-badges/v3_0/index.ts +20 -0
  116. package/src/open-badges/v3_0/ob_v3p0_achievementcredential_schema.ts +17 -0
  117. package/src/open-badges/v3_0/ob_v3p0_endorsementcredential_schema.ts +11 -0
  118. package/src/open-badges/v3_0/ob_v3p0_getopenbadgecredentialsresponse_schema.ts +1 -0
  119. package/src/open-badges/v3_0/ob_v3p0_imsx_statusinfo_schema.ts +8 -0
  120. package/src/open-badges/v3_0/ob_v3p0_profile_schema.ts +9 -0
  121. package/src/open-badges/v3_0/shared.ts +458 -0
  122. package/src/qti/v2-internal.ts +1683 -0
  123. package/src/qti/v2_1/apipv1p0_qtiextv2p1_v1p0.ts +3 -0
  124. package/src/qti/v2_1/imsqti_metadata_v2p1.ts +3 -0
  125. package/src/qti/v2_1/imsqti_result_v2p1.ts +3 -0
  126. package/src/qti/v2_1/imsqti_usagedata_v2p1.ts +3 -0
  127. package/src/qti/v2_1/imsqti_v2p1p2.ts +71 -0
  128. package/src/qti/v2_1/index.ts +53 -0
  129. package/src/qti/v2_1/qtiv2p1_imscpv1p2_v1p0.ts +3 -0
  130. package/src/qti/v2_1/schemas.ts +15 -0
  131. package/src/qti/v2_1/shared.ts +3 -0
  132. package/src/qti/v2_2/apipv1p0_qtiextv2p2_v1p0p1.ts +3 -0
  133. package/src/qti/v2_2/imsqti_metadata_v2p2.ts +3 -0
  134. package/src/qti/v2_2/imsqti_result_v2p2.ts +3 -0
  135. package/src/qti/v2_2/imsqti_usagedata_v2p2.ts +3 -0
  136. package/src/qti/v2_2/imsqti_v2p2p4.ts +76 -0
  137. package/src/qti/v2_2/imsqtiv2p2p4_html5_v1p0.ts +8 -0
  138. package/src/qti/v2_2/index.ts +59 -0
  139. package/src/qti/v2_2/qtiv2p2_csm_v2p2.ts +4 -0
  140. package/src/qti/v2_2/qtiv2p2_imscpv1p2_v1p0.ts +3 -0
  141. package/src/qti/v2_2/schemas.ts +19 -0
  142. package/src/qti/v2_2/shared.ts +3 -0
  143. package/src/qti/v3_0_1/assessment-internal.ts +1509 -0
  144. package/src/qti/v3_0_1/imsqti_asiv3p0p1_v1p0.ts +57 -0
  145. package/src/qti/v3_0_1/imsqti_itemv3p0p1_v1p0.ts +60 -0
  146. package/src/qti/v3_0_1/imsqti_metadatav3p0_v1p0.ts +73 -0
  147. package/src/qti/v3_0_1/imsqti_outcomev3p0p1_v1p0.ts +11 -0
  148. package/src/qti/v3_0_1/imsqti_responseprocessingv3p0p1_v1p0.ts +17 -0
  149. package/src/qti/v3_0_1/imsqti_resultv3p0_v1p0.ts +222 -0
  150. package/src/qti/v3_0_1/imsqti_sectionv3p0p1_v1p0.ts +15 -0
  151. package/src/qti/v3_0_1/imsqti_stimulusv3p0p1_v1p0.ts +15 -0
  152. package/src/qti/v3_0_1/imsqti_testv3p0p1_v1p0.ts +49 -0
  153. package/src/qti/v3_0_1/imsqti_usagedatav3p0_v1p0.ts +77 -0
  154. package/src/qti/v3_0_1/imsqtiv3p0_afa3p0pnp_v1p0.ts +269 -0
  155. package/src/qti/v3_0_1/index.ts +50 -0
  156. package/src/qti/v3_0_1/processing-internal.ts +667 -0
  157. package/src/qti/v3_0_1/shared.ts +146 -0
  158. package/src/qti/v3_0_1/variables-internal.ts +274 -0
  159. package/src/shared/imsx-status.ts +49 -0
  160. package/src/summary.ts +70 -0
  161. package/src/vc-data-model/v2_0/index.ts +27 -0
  162. package/src/vc-data-model/v2_0/shared.ts +206 -0
  163. package/src/xapi/index.ts +12 -0
  164. package/src/xapi/shared.ts +444 -0
  165. package/src/xapi/v1_0_3/index.ts +169 -0
  166. package/src/xapi/v2_0/index.ts +256 -0
  167. package/test/caliper-v1_2.test.ts +270 -0
  168. package/test/case-v1_1.test.ts +147 -0
  169. package/test/cat-v1_0.test.ts +338 -0
  170. package/test/cc-v1_4.test.ts +239 -0
  171. package/test/clr-v2_0.test.ts +225 -0
  172. package/test/cmi5-v1_0.test.ts +196 -0
  173. package/test/contracts.test.ts +125 -0
  174. package/test/fixtures/cmi5/extended-cmi5.xml +72 -0
  175. package/test/fixtures/cmi5/simple-cmi5.xml +26 -0
  176. package/test/lti.test.ts +146 -0
  177. package/test/oneroster-v1_2.test.ts +234 -0
  178. package/test/open-badges-v3_0.test.ts +144 -0
  179. package/test/qti-v2_1.test.ts +146 -0
  180. package/test/qti-v2_2.test.ts +154 -0
  181. package/test/qti-v3_0_1.test.ts +576 -0
  182. package/test/schema-examples.test.ts +101 -0
  183. package/test/vc-data-model-v2_0.test.ts +63 -0
  184. package/test/xapi.test.ts +384 -0
  185. package/tsconfig.json +7 -0
  186. package/vc-data-model-v2_0-zod-templates.md +43 -0
  187. package/xapi-zod-templates.md +95 -0
@@ -0,0 +1,421 @@
1
+ # QTI 3.0.1 XSD bundle → Zod template notes
2
+
3
+ ## Published specification
4
+
5
+ - Overview: `https://www.imsglobal.org/spec/qti/v3p0/oview/`
6
+ - QTI 3.0.1 ASI information model: `https://www.imsglobal.org/sites/default/files/spec/qti/v3/info/imsqti_asi_v3p0p1_infomodel_v1p0.html`
7
+ - QTI 3.0.1 ASI XSD binding: `https://www.imsglobal.org/sites/default/files/spec/qti/v3/bind/imsqti_asi_v3p0p1_xsdbind_v1p0.html`
8
+ - QTI 3 Results Reporting: `https://www.imsglobal.org/sites/default/files/spec/qti/v3/rr-bind/index.html`
9
+ - QTI 3 Usage Data and Item Statistics: `https://www.imsglobal.org/sites/default/files/spec/qti/v3/ud-bind/index.html`
10
+
11
+ This note accompanies the split source under `packages/contracts/src/qti/v3_0_1/`.
12
+
13
+ The TypeScript templates are split primarily by source XSD file, with most of the real structural work concentrated in shared internal modules. This document records the design decisions behind the QTI 3.0.1 port, the places where the XSD maps cleanly to Zod, and the places where the project intentionally chose a normalized JavaScript model over literal XML fidelity.
14
+
15
+ ## QTI Results Reporting assessment (v3)
16
+
17
+ QTI 3 Results Reporting and Usage Data are published as separate QTI companion spec documents, and are represented in this codebase through explicit QTI 3.0.1 result/usage entry points:
18
+
19
+ - `imsqti_resultv3p0_v1p0.ts` provides `QtiAssessmentResultDocumentSchema` and validated result-variable constraints
20
+ - `imsqti_usagedatav3p0_v1p0.ts` provides `QtiUsageDataDocumentSchema` aligned to the QTI 3 Usage Data XSD
21
+ - the v3 namespace exports this via `src/qti/v3_0_1/index.ts` and `Qti301DerivedZodTemplates.qtiAssessmentResultDocument`
22
+ - the v3 namespace also exports `Qti301DerivedZodTemplates.qtiUsageDataDocument`
23
+
24
+ Assessment outcome for v3:
25
+
26
+ 1. Results Reporting and Usage Data are separate published spec artifacts, but fit naturally under the existing `qti/v3_0_1` package surface.
27
+ 2. Core result-reporting and usage-data document support is implemented in the v3_0_1 contracts surface.
28
+ 3. Enhancement-level strictness can continue iteratively (e.g., tighter vocabulary profiles), but no standalone greenfield package is required.
29
+
30
+ ---
31
+
32
+ ## 1. What was analyzed
33
+
34
+ The templates are based on these local XSD files under `tmp/qti/3.0.1`:
35
+
36
+ - `imsqti_asiv3p0p1_v1p0.xsd`
37
+ - `imsqti_itemv3p0p1_v1p0.xsd`
38
+ - `imsqti_metadatav3p0_v1p0.xsd`
39
+ - `imsqti_outcomev3p0p1_v1p0.xsd`
40
+ - `imsqti_responseprocessingv3p0p1_v1p0.xsd`
41
+ - `imsqti_resultv3p0_v1p0.xsd`
42
+ - `imsqti_sectionv3p0p1_v1p0.xsd`
43
+ - `imsqti_stimulusv3p0p1_v1p0.xsd`
44
+ - `imsqti_testv3p0p1_v1p0.xsd`
45
+ - `https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_usagedatav3p0_v1p0.xsd`
46
+ - `imsqtiv3p0_afa3p0pnp_v1p0.xsd`
47
+
48
+ The adjacent `tmp/qti/3.0.1/infos.md` was also used because it explains the intended validation entry points:
49
+
50
+ - the ASI schema is the umbrella validator
51
+ - the other XSDs are resource-specific entry points for standalone package resources
52
+
53
+ Implementation layout:
54
+
55
+ - one TypeScript facade module per source XSD under `src/qti/v3_0_1/`
56
+ - internal support modules for shared structures and semantic rules:
57
+ - `assessment-internal.ts`
58
+ - `variables-internal.ts`
59
+ - `processing-internal.ts`
60
+ - `shared.ts`
61
+ - a bundle barrel at `src/qti/v3_0_1/index.ts`
62
+
63
+ ---
64
+
65
+ ## 2. The main design decision: normalized QTI objects, not literal XML trees
66
+
67
+ ### Problem
68
+
69
+ XSD describes XML grammars. Zod validates JavaScript values. QTI 3.0.1 is especially awkward because it mixes:
70
+
71
+ - XML attributes like `xml:lang`, `xml:base`, `show-hide`, `outcome-identifier`
72
+ - XML extension points and foreign attributes
73
+ - structured QTI domain elements like interactions, declarations, sections, tests, and processing rules
74
+ - very large HTML/XHTML content models embedded inside item, stimulus, rubric, prompt, and feedback bodies
75
+
76
+ The “most XML-faithful” output would be a generic element/attribute tree, but that would make the schemas much less useful as application-facing TypeScript contracts.
77
+
78
+ ### Decision
79
+
80
+ The port deliberately targets a **normalized parsed-XML representation**:
81
+
82
+ - XML attributes become regular JS properties
83
+ - element text becomes string content or `value`, depending on the node shape
84
+ - QTI domain nodes become named Zod object schemas
85
+ - cross-node validation is encoded with `.superRefine()` where the XSD alone is not enough
86
+ - extension points are preserved with explicit foreign-attribute and extension-node shapes
87
+
88
+ ### Rationale
89
+
90
+ This keeps the resulting schemas useful as domain-level validation objects instead of just generic XML containers.
91
+
92
+ ---
93
+
94
+ ## 3. The important HTML-content decision
95
+
96
+ ### Problem
97
+
98
+ The QTI XSD bundle contains a large number of HTML-oriented complex types alongside the core QTI domain types. Treating every HTML content-model type as a first-class hand-modeled Zod schema would add a large amount of surface area without adding much QTI-specific value.
99
+
100
+ At the same time, collapsing all markup to plain `z.string()` would lose important structure:
101
+
102
+ - embedded QTI elements inside content bodies
103
+ - XML include nodes
104
+ - MathML / foreign XML nodes
105
+ - renderable media and image references that QTI interactions depend on structurally
106
+
107
+ ### Decision
108
+
109
+ The implementation **does not** model the HTML vocabulary one schema per XSD type, and it also **does not** reduce all content bodies to `z.string()`.
110
+
111
+ Instead it uses a normalized mixed-content model centered on:
112
+
113
+ - `QtiContentFragmentSchema`
114
+ - `QtiXmlContentNodeSchema`
115
+ - string literals for plain text runs
116
+ - first-class QTI nodes for interactions, feedback, template blocks, printed variables, hotspots, choices, gaps, and similar QTI-specific constructs
117
+
118
+ Renderable XML-ish content that is not worth specializing further is carried through `QtiXmlContentNodeSchema`, which preserves:
119
+
120
+ - element name
121
+ - optional namespace
122
+ - optional attributes
123
+ - optional child nodes
124
+ - optional string value
125
+
126
+ ### Rationale
127
+
128
+ This is the middle ground that kept the port manageable:
129
+
130
+ - **more useful than** turning the entire content model into strings
131
+ - **far smaller than** recreating the entire HTML/XHTML type universe as dedicated schemas
132
+ - still expressive enough for item bodies, prompts, stimuli, and feedback content
133
+
134
+ In other words, the project chose to model **QTI semantics explicitly** and to carry most of the HTML surface as a normalized mixed-content layer.
135
+
136
+ ---
137
+
138
+ ## 4. Why there are “raw”, “document”, and “profile” layers
139
+
140
+ Some parts of the QTI bundle map directly to a root document schema:
141
+
142
+ - item
143
+ - section
144
+ - stimulus
145
+ - test
146
+ - metadata
147
+ - outcome declaration
148
+ - response processing
149
+ - assessment result
150
+ - access-for-all PNP
151
+
152
+ Other parts need more than one useful validation layer.
153
+
154
+ ### Decision
155
+
156
+ The port uses three recurring layers:
157
+
158
+ 1. **raw structural schemas** where the XSD shape itself is useful
159
+ 2. **validated domain schemas** that add `.superRefine()` rules for obvious semantic constraints
160
+ 3. **document/profile unions** for the resource families that the QTI package model treats as interchangeable validation entry points
161
+
162
+ Examples:
163
+
164
+ - `QtiAssessmentItemRawSchema` vs `QtiAssessmentItemSchema`
165
+ - `QtiAssessmentSectionRawSchema` vs `QtiAssessmentSectionSchema`
166
+ - `QtiAssessmentTestRawSchema` vs `QtiAssessmentTestSchema`
167
+ - `QtiItemProfileDocumentSchema`
168
+ - `QtiTestProfileDocumentSchema`
169
+ - `QtiAsiProfileDocumentSchema`
170
+
171
+ ### Rationale
172
+
173
+ This mirrors the way the QTI bundle is actually used:
174
+
175
+ - some validations are “does this object have the declared XML structure?”
176
+ - others are “does this object satisfy the QTI semantics implied by declarations, bindings, and processing rules?”
177
+ - the ASI umbrella schema naturally becomes a union of resource-specific document entry points
178
+
179
+ ---
180
+
181
+ ## 5. File-family mapping decisions
182
+
183
+ ## 5.1 `imsqti_asiv3p0p1_v1p0.xsd`
184
+
185
+ This is the umbrella schema and the main conceptual source for the internal shared model.
186
+
187
+ ### Mapping decision
188
+
189
+ - the public facade exports the shared assessment, variable, and processing structures
190
+ - `QtiAsiProfileDocumentSchema` is modeled as a union of the resource-specific document schemas
191
+ - internal aliases and standalone document schemas are preserved where they help mirror the source bundle structure
192
+
193
+ ### Rationale
194
+
195
+ The ASI schema is not best represented as one giant monolithic Zod object. In practice it is a bundle schema over several resource families, so the union model is clearer and more reusable.
196
+
197
+ ---
198
+
199
+ ## 5.2 `imsqti_itemv3p0p1_v1p0.xsd`, `imsqti_sectionv3p0p1_v1p0.xsd`, `imsqti_stimulusv3p0p1_v1p0.xsd`, `imsqti_testv3p0p1_v1p0.xsd`
200
+
201
+ These are the core assessment-structure XSDs.
202
+
203
+ ### Mapping decision
204
+
205
+ - the heavy lifting lives in `assessment-internal.ts`
206
+ - the facade files stay small and expose the root document schema for each resource
207
+ - item, section, and test schemas add semantic validation around:
208
+ - identifier uniqueness
209
+ - declaration consistency
210
+ - response binding compatibility
211
+ - declared-outcome references from processing rules
212
+ - section selection/order constraints
213
+ - interaction-specific cardinality and bounds checks
214
+ - “feedback content must not contain interactions” rules
215
+
216
+ ### Rationale
217
+
218
+ These schemas are tightly interdependent. Putting the shared structures in one internal module avoids a large amount of duplication while preserving the public “one module per source XSD” shape.
219
+
220
+ ---
221
+
222
+ ## 5.3 `imsqti_outcomev3p0p1_v1p0.xsd` and `imsqti_responseprocessingv3p0p1_v1p0.xsd`
223
+
224
+ These are structurally smaller at the document level, but semantically important because they introduce reusable processing fragments and declarations.
225
+
226
+ ### Mapping decision
227
+
228
+ - root documents stay simple and direct
229
+ - the actual declaration and processing-rule structures live in `variables-internal.ts` and `processing-internal.ts`
230
+ - validated declaration schemas enforce things like:
231
+ - record cardinality omits `baseType`
232
+ - record values require `fieldIdentifier`
233
+ - mapping / areaMapping compatibility
234
+ - mutually exclusive outcome tables
235
+ - sensible min/max ordering
236
+
237
+ ### Rationale
238
+
239
+ The root wrappers are thin; the meaning lives in the shared variable and processing model.
240
+
241
+ ---
242
+
243
+ ## 5.4 `imsqti_metadatav3p0_v1p0.xsd`
244
+
245
+ This is comparatively straightforward.
246
+
247
+ ### Mapping decision
248
+
249
+ - the document root is direct
250
+ - enumerated metadata vocabularies are mapped as enums
251
+ - a small semantic rule ensures `portableCustomInteractionContext` is only present when the interaction list includes `portableCustomInteraction`
252
+
253
+ ### Rationale
254
+
255
+ Metadata is simple enough to model almost literally, with only one obvious consistency check added.
256
+
257
+ ---
258
+
259
+ ## 5.5 `imsqti_resultv3p0_v1p0.xsd`
260
+
261
+ Result reporting is structurally direct, but it has important runtime-model constraints around cardinality and fielded values.
262
+
263
+ ### Mapping decision
264
+
265
+ - separate schemas exist for context, test results, item results, and variable families
266
+ - validated result-variable schemas enforce:
267
+ - `record` cardinality omits `baseType`
268
+ - record-valued entries require `fieldIdentifier`
269
+ - non-record values must not carry `fieldIdentifier`
270
+
271
+ ### Rationale
272
+
273
+ These are good examples of constraints that are easy to lose if the model stops at raw structure. The validated result-variable layer is therefore part of the real document shape, not just an optional extra.
274
+
275
+ ---
276
+
277
+ ## 5.6 `imsqtiv3p0_afa3p0pnp_v1p0.xsd`
278
+
279
+ The PNP schema is a domain-specific preferences/configuration bundle.
280
+
281
+ ### Mapping decision
282
+
283
+ - it is modeled as plain nested objects with enums and booleans where possible
284
+ - targeted semantic checks are added where the XSD intent is operationally obvious, such as:
285
+ - mutually exclusive additional-testing-time controls
286
+ - `allContent` magnification not being combined with `text`/`nonText`
287
+
288
+ ### Rationale
289
+
290
+ This schema is configuration-like rather than markup-heavy, so a direct normalized object model is the most practical fit.
291
+
292
+ ---
293
+
294
+ ## 6. Processing expressions: what is now explicit
295
+
296
+ The processing model originally had a catch-all `QtiOpaqueExpressionSchema` for a set of real QTI operators. That was too generic for the actual project goals.
297
+
298
+ ### Decision
299
+
300
+ These operators are now modeled as first-class schemas:
301
+
302
+ - `anyN`
303
+ - `customOperator`
304
+ - `equalRounded`
305
+ - `fieldValue`
306
+ - `index`
307
+ - `inside`
308
+ - `mathOperator`
309
+ - `patternMatch`
310
+ - `repeat`
311
+ - `roundTo`
312
+ - `statsOperator`
313
+ - `stringMatch`
314
+ - `substring`
315
+ - `testVariables`
316
+ - `numberCorrect`
317
+ - `numberIncorrect`
318
+ - `numberPresented`
319
+ - `numberResponded`
320
+ - `numberSelected`
321
+ - `outcomeMinimum`
322
+ - `outcomeMaximum`
323
+
324
+ The binary operator family also explicitly includes:
325
+
326
+ - `integerDivide`
327
+ - `integerModulus`
328
+
329
+ ### Semantic rules added
330
+
331
+ Where the XSD intent is unambiguous and cheap to enforce, the processing layer also validates:
332
+
333
+ - numeric min/max ordering for `randomInteger` and `randomFloat`
334
+ - valid range and child-count relationships for `anyN`
335
+ - positive literal indexes / repeat counts / figures where appropriate
336
+ - `mathOperator` arity, especially `atan2`
337
+
338
+ ### Rationale
339
+
340
+ These are real QTI domain operators, not just lexical XML details. Leaving them opaque would have made the port structurally incomplete in one of the most important parts of the spec.
341
+
342
+ ---
343
+
344
+ ## 7. What is still intentionally approximate
345
+
346
+ ### 7.1 Full HTML/XHTML lexical fidelity
347
+
348
+ The port does not recreate every HTML complex type as a dedicated first-class Zod schema.
349
+
350
+ That is intentional.
351
+
352
+ ### 7.2 Full XML namespace/prefix fidelity
353
+
354
+ The model preserves extension/foreign-node information, but it does not attempt to preserve every original XML lexical distinction exactly as written in the source document.
355
+
356
+ ### 7.3 `customOperator` behavior
357
+
358
+ `customOperator` is structurally modeled, but its runtime meaning is intentionally open-ended because the spec itself presents it as an extension point.
359
+
360
+ ### 7.4 Some processing semantics remain type-level rather than evaluation-level
361
+
362
+ The Zod schemas validate object shape and a useful subset of semantic invariants. They do not attempt to fully execute QTI processing semantics or prove all runtime typing rules for every expression tree.
363
+
364
+ ### Rationale
365
+
366
+ These limitations are acceptable because the project goal is a practical schema layer for parsed QTI objects, not a full XML round-tripper or a QTI execution engine.
367
+
368
+ ---
369
+
370
+ ## 8. How to interpret the output structure
371
+
372
+ The main public entry points are:
373
+
374
+ - `packages/contracts/src/qti/v3_0_1/index.ts`
375
+ - `Qti301DerivedZodTemplates`
376
+
377
+ Important groupings:
378
+
379
+ - **resource document schemas**
380
+ - `QtiAssessmentItemDocumentSchema`
381
+ - `QtiAssessmentSectionDocumentSchema`
382
+ - `QtiAssessmentStimulusDocumentSchema`
383
+ - `QtiAssessmentTestDocumentSchema`
384
+ - `QtiOutcomeDeclarationDocumentSchema`
385
+ - `QtiOutcomeProcessingDocumentSchema`
386
+ - `QtiResponseProcessingDocumentSchema`
387
+ - `QtiMetadataDocumentSchema`
388
+ - `QtiAssessmentResultDocumentSchema`
389
+ - `QtiAccessForAllPnpDocumentSchema`
390
+ - `QtiAccessForAllPnpRecordsDocumentSchema`
391
+
392
+ - **profile unions**
393
+ - `QtiItemProfileDocumentSchema`
394
+ - `QtiTestProfileDocumentSchema`
395
+ - `QtiAsiProfileDocumentSchema`
396
+
397
+ - **shared XML helpers**
398
+ - `QtiXmlExtensionNodeSchema`
399
+ - `QtiXmlExtensionNodeListSchema`
400
+
401
+ - **internal structural hubs**
402
+ - `assessment-internal.ts`
403
+ - `variables-internal.ts`
404
+ - `processing-internal.ts`
405
+
406
+ The mental model is:
407
+
408
+ - use the facade files and barrel for stable public document schemas
409
+ - treat the internal modules as the normalized shared model backing those documents
410
+
411
+ ---
412
+
413
+ ## 9. If this were taken further
414
+
415
+ The next likely improvements would be:
416
+
417
+ 1. more expression-level semantic validation, especially where variable typing across processing trees can be checked cheaply
418
+ 2. more negative tests around profile unions and cross-document invariants
419
+ 3. deeper specialization of selected HTML-adjacent content nodes only where that adds clear QTI-specific value
420
+
421
+ The important point is that the project is already beyond “raw structural scaffolding”: it now has a substantial domain model, real semantic constraints, and a documented rationale for the places where it intentionally stays approximate.
package/src/adapter.ts ADDED
@@ -0,0 +1,83 @@
1
+ import { z } from "zod";
2
+
3
+ export const AdapterProfileSuiteSchema = z.enum(["cmi5", "lti13", "lrs"]);
4
+ export const AdapterTransportSchema = z.enum(["http-json"]);
5
+ export const StatementRetrievalModeSchema = z.enum(["adapter-api"]);
6
+ export const PackageUploadModeSchema = z.enum(["inline-base64"]);
7
+
8
+ export const AdapterErrorCategorySchema = z.enum([
9
+ "validation",
10
+ "unauthorized",
11
+ "forbidden",
12
+ "not_found",
13
+ "conflict",
14
+ "upstream_unavailable",
15
+ "internal",
16
+ ]);
17
+
18
+ export const AdapterCapabilitySchema = z.object({
19
+ profileVersion: z.string().regex(/^\d+\.\d+\.\d+$/u),
20
+ adapterName: z.string().min(1),
21
+ adapterVersion: z.string().min(1),
22
+ operations: z.array(z.string().min(1)),
23
+ profiles: z.array(z.string().min(1)).default([]),
24
+ optionalFeatures: z.array(z.string().min(1)).default([]),
25
+ });
26
+
27
+ export const AdapterOperationSchema = z.object({
28
+ name: z.string().min(1),
29
+ path: z.string().min(1),
30
+ method: z.enum(["GET", "POST"]),
31
+ description: z.string().min(1).optional(),
32
+ });
33
+
34
+ export const AdapterProfileSchema = z
35
+ .object({
36
+ contractVersion: z.string().regex(/^\d+\.\d+\.\d+$/u),
37
+ profileVersion: z.string().regex(/^\d+\.\d+\.\d+$/u),
38
+ suite: AdapterProfileSuiteSchema,
39
+ adapter: z.object({
40
+ name: z.string().min(1),
41
+ version: z.string().min(1),
42
+ transport: AdapterTransportSchema,
43
+ }),
44
+ interoperability: z.object({
45
+ statementRetrieval: StatementRetrievalModeSchema,
46
+ packageUpload: PackageUploadModeSchema.optional(),
47
+ }),
48
+ operations: z.array(AdapterOperationSchema).min(1),
49
+ artifacts: z.object({
50
+ requirementTraceRequired: z.literal(true),
51
+ }),
52
+ })
53
+ .superRefine((profile, context) => {
54
+ if (profile.suite === "cmi5" && profile.interoperability.packageUpload !== "inline-base64") {
55
+ context.addIssue({
56
+ code: z.ZodIssueCode.custom,
57
+ message: "interoperability.packageUpload must be inline-base64 for cmi5 profiles",
58
+ path: ["interoperability", "packageUpload"],
59
+ });
60
+ }
61
+ });
62
+
63
+ export const AdapterErrorSchema = z.object({
64
+ error: z.object({
65
+ code: z.string().min(1),
66
+ message: z.string().min(1),
67
+ category: AdapterErrorCategorySchema,
68
+ retriable: z.boolean(),
69
+ details: z.record(z.string(), z.unknown()).optional(),
70
+ upstreamStatus: z.number().int().optional(),
71
+ }),
72
+ });
73
+
74
+ export type AdapterCapability = z.infer<typeof AdapterCapabilitySchema>;
75
+ export type AdapterOperation = z.infer<typeof AdapterOperationSchema>;
76
+ export type AdapterProfile = z.infer<typeof AdapterProfileSchema>;
77
+ export type AdapterError = z.infer<typeof AdapterErrorSchema>;
78
+ // Inferred types from exported Zod validators.
79
+ export type AdapterProfileSuite = z.infer<typeof AdapterProfileSuiteSchema>;
80
+ export type AdapterTransport = z.infer<typeof AdapterTransportSchema>;
81
+ export type StatementRetrievalMode = z.infer<typeof StatementRetrievalModeSchema>;
82
+ export type PackageUploadMode = z.infer<typeof PackageUploadModeSchema>;
83
+ export type AdapterErrorCategory = z.infer<typeof AdapterErrorCategorySchema>;