QTI 3.0.1 authored-content ARIA accessibility, and honest disposition of the QTI model-coverage gaps
Status: accepted (2026-06-25)
The QTI 3.0.1 Coverage Map (conform-ed ADR-0013, emergent ADR-0028) read 224/5344
modelled with 358 silent gaps, blanket-triaged into 21 deferred gapGroups in
docs/conformance/overlays/qti-v3.0.1.yaml. A line-by-line audit against conform-ed’s
actual contracts showed that blanket deferral was the wrong story: the L2 reconciler joins
the literal ASI XSD to conform-ed’s Zod by property name, and ~311 of the 358 “gaps” are
constructs conform-ed does model under a different shape the name-join structurally cannot
see — not absent capabilities. Leaving them as deferred (which means planned-but-unbuilt)
is exactly the prose-drift the board exists to kill.
Decision
Section titled “Decision”Give every one of the 358 gaps its honest disposition, per cluster:
| Cluster | Count | Disposition | Why |
|---|---|---|---|
Expression / processing operators (qti-and, qti-sum, qti-set-outcome-value, …) |
146 | Normalise (conform-ed residues.normalisations) |
conform-ed models every operator + rule as a kind-discriminated union (expression/rules Zod properties); the XSD names them as elements of BranchRuleDType/LogicSingleDType/*ProcessingDType. Modelled differently, not absent. |
Interaction / block children + structural-tail renames (qti-choice-interaction …, qti-map-entry, nested qti-assessment-section, qti-content-body, xml:lang) |
~40 | Normalise | conform-ed models all 22 interactions as typed QtiContentFragment union members (emergent authors them), entries as arrays (mapEntries …), nested sections as typed children, xml:lang as lang/xmlLang. |
Generic XHTML flow/inline vocabulary (a, p, div, table, img, video, …) |
~110 | Normalise, opaque-passthrough note | conform-ed carries these via a generic opaque XML content node ({kind:"xml", name, attributes, children}) — lossless round-trip (the official corpus passes 315/327 through emergent’s pipeline), but not per-element schema. The normalisation note states this explicitly so modelled:yes is not misread as typed per-element validation. |
ARIA attributes + role |
47 | BUILD (this ADR) | conform-ed carried ARIA in a generic untyped ariaAttributes bag, never surfaced in authoring and never rendered onto QTI interaction DOM roots. Intersects the standing accessibility mandate — the one cluster that warrants real work, not reclassification. |
The mechanism for the ~311 normalisations is conform-ed’s existing ADR-0017 machinery —
structuralAliases bridging the Zod expression/rules/content/children/*Entries
properties to the literal element names, plus a specRefOverride for xml:lang→xmlLang
mirroring the existing xml:base one. Absorbed keys leave silentGaps and move into
residues.normalisations; the corresponding emergent gapGroups are deleted (a silent gap,
by definition unmodelled, can never be done — so honest closure lives upstream, not as an
overlay relabel).
The ARIA build (cross-repo)
Section titled “The ARIA build (cross-repo)”- conform-ed model — type the 47 ARIA attributes +
roleas a namedariaAttributesshape (value-sets for the closed enumerations:aria-checkedetc.), bridged so the 47ARIABaseDType/aria-*literal items become genuinelymodelled:yes(typed, value-set checked) — not opaque. - conform-ed delivery — the reference skin / runtime spreads the sanitized
ariaAttributesbag onto each interaction’s render root, so ARIA authored on a QTI element (not just on generic HTML, which already renders) reaches assistive tech. - conform-ed conformance — a new
QTI-A11Yrequirement: authored content / interactions MAY carry ARIA roles/states; they are preserved through emit and rendered to the delivery DOM. Cited against the ASI §2.13.3 WAI-ARIA attribute schema. - emergent — surface ARIA fields in the item author panel across interaction kinds
(full scope), and a
[covers: qti:3.0.1:conf:…/QTI-A11Y-*]test proving ARIA on a QTI element survives emergent’s real ingest→serialize(→render) path.
Considered alternatives
Section titled “Considered alternatives”- Keep the 21
deferredgapGroups with sharpened reasons. Rejected: dishonest — the constructs are modelled (verified inprocessing-internal.ts/assessment-internal.ts). - Normalise ARIA like generic HTML (it uses the same generic bag). Rejected: honest at the model level but advances accessibility nothing, and dodges the standing mandate.
- Build typed per-element XHTML schemas. Rejected: large blast radius, low value — the generic passthrough + emergent’s structured authoring already cover real needs.
Consequences
Section titled “Consequences”- Honest closure is conform-ed-side (normalisations) for ~311 items; the emergent overlay’s
gapGroupsshrink from 21 to (target) ~0, andsilentGaps→ ~0. - Cross-repo sequencing: conform-ed lands first; Anton FFs conform-ed
main→ CI publishes@dev; emergent re-pins (coverageMapSha256refresh) and only then rewrites the overlay, builds the authoring UI, and adds the proof. - The
normalisationsrollup figure is the standing honesty disclosure that ~311 items are “modelled differently” (incl. opaque generic-content representation), not typed per-node.