@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.
- package/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-format.log +6 -0
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +196 -0
- package/.turbo/turbo-typecheck.log +2 -0
- package/README.md +52 -0
- package/caliper-v1_2-zod-templates.md +123 -0
- package/case-v1_1-zod-templates.md +290 -0
- package/cat-v1_0-zod-templates.md +73 -0
- package/cc-v1_3-zod-templates.md +531 -0
- package/cc-v1_4-zod-templates.md +247 -0
- package/clr-v2_0-zod-templates.md +117 -0
- package/cmi5-v1_0-zod-templates.md +9 -0
- package/lti-zod-templates.md +11 -0
- package/oneroster-v1_2-zod-templates.md +76 -0
- package/open-badges-v3_0-zod-templates.md +66 -0
- package/package.json +42 -0
- package/qti-v2_1-zod-templates.md +45 -0
- package/qti-v2_2-zod-templates.md +32 -0
- package/qti-v3_0_1-zod-templates.md +421 -0
- package/src/adapter.ts +83 -0
- package/src/caliper/v1_2/caliper_v1p2_bootcamp_schema.ts +781 -0
- package/src/caliper/v1_2/index.ts +37 -0
- package/src/caliper/v1_2/shared.ts +122 -0
- package/src/caliper/v1_2/textual_requirements.ts +219 -0
- package/src/case/v1_1/case_v1p1_cfassociation_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfassociationgrouping_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfassociationset_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfconceptset_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfdocument_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfdocumentset_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfitem_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfitemtypeset_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cflicense_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfpackage_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfrubric_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_cfsubjectset_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_imsx_statusinfo_jsonschema1.ts +1 -0
- package/src/case/v1_1/case_v1p1_openapi3_restbinding_schema.ts +113 -0
- package/src/case/v1_1/index.ts +95 -0
- package/src/case/v1_1/shared.ts +384 -0
- package/src/cat/v1_0/cat_v1p0_restbinding_operations_schema.ts +75 -0
- package/src/cat/v1_0/index.ts +124 -0
- package/src/cat/v1_0/shared.ts +345 -0
- package/src/clr/v2_0/clr_v2p0_achievementcredential_schema.ts +16 -0
- package/src/clr/v2_0/clr_v2p0_clrcredential_schema.ts +9 -0
- package/src/clr/v2_0/clr_v2p0_endorsementcredential_schema.ts +11 -0
- package/src/clr/v2_0/clr_v2p0_getclrcredentialsresponse_schema.ts +1 -0
- package/src/clr/v2_0/clr_v2p0_imsx_statusinfo_schema.ts +8 -0
- package/src/clr/v2_0/clr_v2p0_profile_schema.ts +9 -0
- package/src/clr/v2_0/index.ts +22 -0
- package/src/clr/v2_0/shared.ts +206 -0
- package/src/cmi5/index.ts +3 -0
- package/src/cmi5/v1_0/index.ts +134 -0
- package/src/common-cartridge/v1_3/ccv1p3_imsccauth_v1p3.ts +26 -0
- package/src/common-cartridge/v1_3/ccv1p3_imscp_v1p2_v1p0.ts +352 -0
- package/src/common-cartridge/v1_3/ccv1p3_imscsmd_v1p0.ts +35 -0
- package/src/common-cartridge/v1_3/ccv1p3_imsdt_v1p3.ts +33 -0
- package/src/common-cartridge/v1_3/ccv1p3_imswl_v1p3.ts +23 -0
- package/src/common-cartridge/v1_3/ccv1p3_lomccltilink_v1p0.ts +14 -0
- package/src/common-cartridge/v1_3/ccv1p3_lommanifest_v1p0.ts +14 -0
- package/src/common-cartridge/v1_3/ccv1p3_lomresource_v1p0.ts +14 -0
- package/src/common-cartridge/v1_3/ccv1p3_qtiasiv1p2p1_v1p0.ts +964 -0
- package/src/common-cartridge/v1_3/index.ts +41 -0
- package/src/common-cartridge/v1_3/lom-internal.ts +396 -0
- package/src/common-cartridge/v1_3/shared.ts +68 -0
- package/src/common-cartridge/v1_4/core/ccv1p4_imscp_v1p2_v1p0.ts +360 -0
- package/src/common-cartridge/v1_4/core/ccv1p4_lommanifest_v1p0.ts +14 -0
- package/src/common-cartridge/v1_4/core/ccv1p4_lomresource_v1p0.ts +14 -0
- package/src/common-cartridge/v1_4/extension/cc_extresource_assignmentv1p0_v1p0.ts +61 -0
- package/src/common-cartridge/v1_4/extension/ccv1p4_cpextensionv1p2_v1p0.ts +20 -0
- package/src/common-cartridge/v1_4/extension/ims_openvideov1p0_v1p0.ts +325 -0
- package/src/common-cartridge/v1_4/index.ts +104 -0
- package/src/common-cartridge/v1_4/k12/ccv1p4_imscp_v1p2_v1p0.ts +19 -0
- package/src/common-cartridge/v1_4/k12/ccv1p4_imscp_v1p2_v1p0_thin.ts +17 -0
- package/src/common-cartridge/v1_4/k12/ccv1p4_lommanifest_v1p0.ts +14 -0
- package/src/common-cartridge/v1_4/k12/ccv1p4_lomresource_v1p0.ts +14 -0
- package/src/common-cartridge/v1_4/lom-internal.ts +476 -0
- package/src/common-cartridge/v1_4/shared/ccv1p4_imsccauth_v1p4.ts +26 -0
- package/src/common-cartridge/v1_4/shared/ccv1p4_imscsmd_v1p1.ts +36 -0
- package/src/common-cartridge/v1_4/shared/ccv1p4_imsdt_v1p4.ts +33 -0
- package/src/common-cartridge/v1_4/shared/ccv1p4_imslticc_v1p4.ts +45 -0
- package/src/common-cartridge/v1_4/shared/ccv1p4_imswl_v1p4.ts +23 -0
- package/src/common-cartridge/v1_4/shared/ccv1p4_lomccltilink_v1p0.ts +14 -0
- package/src/common-cartridge/v1_4/shared/ccv1p4_qtiasiv1p2p1_v1p0.ts +964 -0
- package/src/common-cartridge/v1_4/shared/imsbasiclti_v1p0p1.ts +24 -0
- package/src/common-cartridge/v1_4/shared/imslticm_v1p0.ts +23 -0
- package/src/common-cartridge/v1_4/shared/imslticp_v1p0.ts +57 -0
- package/src/common-cartridge/v1_4/shared/lineitem_v1p0.ts +35 -0
- package/src/common-cartridge/v1_4/shared/resourcea11ymetadata-20210915.ts +110 -0
- package/src/common-cartridge/v1_4/shared.ts +68 -0
- package/src/common-cartridge/v1_4/thin/ccv1p4_imscp_v1p2_v1p0.ts +243 -0
- package/src/common-cartridge/v1_4/thin/ccv1p4_lommanifest_v1p0.ts +14 -0
- package/src/common-cartridge/v1_4/thin/ccv1p4_lomresource_v1p0.ts +14 -0
- package/src/common-cartridge/v1_4/vdex/imsmd_loose_v1p3p2.ts +13 -0
- package/src/common-cartridge/v1_4/vdex/imsvdex_v1p0.ts +124 -0
- package/src/config.ts +121 -0
- package/src/index.ts +32 -0
- package/src/lti/ags/v2_0/index.ts +97 -0
- package/src/lti/deep-linking/v2_0/index.ts +100 -0
- package/src/lti/index.ts +26 -0
- package/src/lti/nrps/v2_0/index.ts +69 -0
- package/src/lti/proctoring/v1_0/index.ts +131 -0
- package/src/lti/shared.ts +66 -0
- package/src/lti/v1_3/index.ts +84 -0
- package/src/oneroster/v1_2/index.ts +156 -0
- package/src/oneroster/v1_2/or_v1p2_csv_binding_schema.ts +427 -0
- package/src/oneroster/v1_2/or_v1p2_gradebook_restbinding_schema.ts +120 -0
- package/src/oneroster/v1_2/or_v1p2_gradebook_service_schema.ts +243 -0
- package/src/oneroster/v1_2/or_v1p2_resource_restbinding_schema.ts +24 -0
- package/src/oneroster/v1_2/or_v1p2_resource_service_schema.ts +47 -0
- package/src/oneroster/v1_2/or_v1p2_rostering_restbinding_schema.ts +84 -0
- package/src/oneroster/v1_2/or_v1p2_rostering_service_schema.ts +288 -0
- package/src/oneroster/v1_2/shared.ts +90 -0
- package/src/open-badges/v3_0/index.ts +20 -0
- package/src/open-badges/v3_0/ob_v3p0_achievementcredential_schema.ts +17 -0
- package/src/open-badges/v3_0/ob_v3p0_endorsementcredential_schema.ts +11 -0
- package/src/open-badges/v3_0/ob_v3p0_getopenbadgecredentialsresponse_schema.ts +1 -0
- package/src/open-badges/v3_0/ob_v3p0_imsx_statusinfo_schema.ts +8 -0
- package/src/open-badges/v3_0/ob_v3p0_profile_schema.ts +9 -0
- package/src/open-badges/v3_0/shared.ts +458 -0
- package/src/qti/v2-internal.ts +1683 -0
- package/src/qti/v2_1/apipv1p0_qtiextv2p1_v1p0.ts +3 -0
- package/src/qti/v2_1/imsqti_metadata_v2p1.ts +3 -0
- package/src/qti/v2_1/imsqti_result_v2p1.ts +3 -0
- package/src/qti/v2_1/imsqti_usagedata_v2p1.ts +3 -0
- package/src/qti/v2_1/imsqti_v2p1p2.ts +71 -0
- package/src/qti/v2_1/index.ts +53 -0
- package/src/qti/v2_1/qtiv2p1_imscpv1p2_v1p0.ts +3 -0
- package/src/qti/v2_1/schemas.ts +15 -0
- package/src/qti/v2_1/shared.ts +3 -0
- package/src/qti/v2_2/apipv1p0_qtiextv2p2_v1p0p1.ts +3 -0
- package/src/qti/v2_2/imsqti_metadata_v2p2.ts +3 -0
- package/src/qti/v2_2/imsqti_result_v2p2.ts +3 -0
- package/src/qti/v2_2/imsqti_usagedata_v2p2.ts +3 -0
- package/src/qti/v2_2/imsqti_v2p2p4.ts +76 -0
- package/src/qti/v2_2/imsqtiv2p2p4_html5_v1p0.ts +8 -0
- package/src/qti/v2_2/index.ts +59 -0
- package/src/qti/v2_2/qtiv2p2_csm_v2p2.ts +4 -0
- package/src/qti/v2_2/qtiv2p2_imscpv1p2_v1p0.ts +3 -0
- package/src/qti/v2_2/schemas.ts +19 -0
- package/src/qti/v2_2/shared.ts +3 -0
- package/src/qti/v3_0_1/assessment-internal.ts +1509 -0
- package/src/qti/v3_0_1/imsqti_asiv3p0p1_v1p0.ts +57 -0
- package/src/qti/v3_0_1/imsqti_itemv3p0p1_v1p0.ts +60 -0
- package/src/qti/v3_0_1/imsqti_metadatav3p0_v1p0.ts +73 -0
- package/src/qti/v3_0_1/imsqti_outcomev3p0p1_v1p0.ts +11 -0
- package/src/qti/v3_0_1/imsqti_responseprocessingv3p0p1_v1p0.ts +17 -0
- package/src/qti/v3_0_1/imsqti_resultv3p0_v1p0.ts +222 -0
- package/src/qti/v3_0_1/imsqti_sectionv3p0p1_v1p0.ts +15 -0
- package/src/qti/v3_0_1/imsqti_stimulusv3p0p1_v1p0.ts +15 -0
- package/src/qti/v3_0_1/imsqti_testv3p0p1_v1p0.ts +49 -0
- package/src/qti/v3_0_1/imsqti_usagedatav3p0_v1p0.ts +77 -0
- package/src/qti/v3_0_1/imsqtiv3p0_afa3p0pnp_v1p0.ts +269 -0
- package/src/qti/v3_0_1/index.ts +50 -0
- package/src/qti/v3_0_1/processing-internal.ts +667 -0
- package/src/qti/v3_0_1/shared.ts +146 -0
- package/src/qti/v3_0_1/variables-internal.ts +274 -0
- package/src/shared/imsx-status.ts +49 -0
- package/src/summary.ts +70 -0
- package/src/vc-data-model/v2_0/index.ts +27 -0
- package/src/vc-data-model/v2_0/shared.ts +206 -0
- package/src/xapi/index.ts +12 -0
- package/src/xapi/shared.ts +444 -0
- package/src/xapi/v1_0_3/index.ts +169 -0
- package/src/xapi/v2_0/index.ts +256 -0
- package/test/caliper-v1_2.test.ts +270 -0
- package/test/case-v1_1.test.ts +147 -0
- package/test/cat-v1_0.test.ts +338 -0
- package/test/cc-v1_4.test.ts +239 -0
- package/test/clr-v2_0.test.ts +225 -0
- package/test/cmi5-v1_0.test.ts +196 -0
- package/test/contracts.test.ts +125 -0
- package/test/fixtures/cmi5/extended-cmi5.xml +72 -0
- package/test/fixtures/cmi5/simple-cmi5.xml +26 -0
- package/test/lti.test.ts +146 -0
- package/test/oneroster-v1_2.test.ts +234 -0
- package/test/open-badges-v3_0.test.ts +144 -0
- package/test/qti-v2_1.test.ts +146 -0
- package/test/qti-v2_2.test.ts +154 -0
- package/test/qti-v3_0_1.test.ts +576 -0
- package/test/schema-examples.test.ts +101 -0
- package/test/vc-data-model-v2_0.test.ts +63 -0
- package/test/xapi.test.ts +384 -0
- package/tsconfig.json +7 -0
- package/vc-data-model-v2_0-zod-templates.md +43 -0
- 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>;
|