@longsightgroup/qti3-core 0.9.0 → 0.9.2

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 (51) hide show
  1. package/README.md +39 -0
  2. package/dist/adaptive-turn-materializer.d.ts +14 -0
  3. package/dist/adaptive-turn-materializer.d.ts.map +1 -0
  4. package/dist/adaptive-turn-materializer.js +56 -0
  5. package/dist/adaptive-turn-materializer.js.map +1 -0
  6. package/dist/adaptive-turn.d.ts +23 -0
  7. package/dist/adaptive-turn.d.ts.map +1 -0
  8. package/dist/adaptive-turn.js +60 -0
  9. package/dist/adaptive-turn.js.map +1 -0
  10. package/dist/asset-url.d.ts.map +1 -1
  11. package/dist/asset-url.js +1 -0
  12. package/dist/asset-url.js.map +1 -1
  13. package/dist/delivery-redaction.d.ts +50 -0
  14. package/dist/delivery-redaction.d.ts.map +1 -0
  15. package/dist/delivery-redaction.js +176 -0
  16. package/dist/delivery-redaction.js.map +1 -0
  17. package/dist/delivery-security.d.ts +3 -18
  18. package/dist/delivery-security.d.ts.map +1 -1
  19. package/dist/delivery-security.js +35 -189
  20. package/dist/delivery-security.js.map +1 -1
  21. package/dist/index.d.ts +2 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/server-scoring.d.ts +4 -14
  26. package/dist/server-scoring.d.ts.map +1 -1
  27. package/dist/server-scoring.js +19 -157
  28. package/dist/server-scoring.js.map +1 -1
  29. package/dist/shared-vocabulary-support.d.ts.map +1 -1
  30. package/dist/shared-vocabulary-support.js +1 -0
  31. package/dist/shared-vocabulary-support.js.map +1 -1
  32. package/dist/support.d.ts.map +1 -1
  33. package/dist/support.js +8 -4
  34. package/dist/support.js.map +1 -1
  35. package/dist/trusted-item-session.d.ts +49 -0
  36. package/dist/trusted-item-session.d.ts.map +1 -0
  37. package/dist/trusted-item-session.js +260 -0
  38. package/dist/trusted-item-session.js.map +1 -0
  39. package/package.json +4 -2
  40. package/src/adaptive-turn-materializer.ts +85 -0
  41. package/src/adaptive-turn.ts +109 -0
  42. package/src/asset-url.ts +1 -0
  43. package/src/delivery-redaction.ts +268 -0
  44. package/src/delivery-security.ts +59 -245
  45. package/src/index.ts +7 -0
  46. package/src/server-scoring.ts +30 -226
  47. package/src/shared-vocabulary-support.ts +1 -0
  48. package/src/support.ts +9 -4
  49. package/src/trusted-item-session.ts +427 -0
  50. package/src/interaction-test-fixtures.ts +0 -44
  51. package/src/serializer-processing.fixtures.ts +0 -469
package/README.md CHANGED
@@ -109,6 +109,45 @@ The delivery redaction and server-scoring APIs are library APIs in `0.5.x`. CLI
109
109
  for delivery-safe XML generation and server-style scoring are not part of this release
110
110
  line yet.
111
111
 
112
+ ### Secure adaptive turns
113
+
114
+ Adaptive item delivery should run each submitted turn against authoritative XML on the
115
+ server, preserving prior `qti3.attempt-state.v1` between turns:
116
+
117
+ ```ts
118
+ import { processQtiAdaptiveItemTurn } from "@longsightgroup/qti3-core";
119
+
120
+ const turn = processQtiAdaptiveItemTurn({
121
+ itemXml: authoritativeItemXml,
122
+ priorState: savedAttemptState,
123
+ trustedResponses: { RESPONSE: "A" },
124
+ });
125
+
126
+ if (!turn.ok) {
127
+ throw new Error(turn.diagnostics.map((item) => item.message).join("; "));
128
+ }
129
+
130
+ store(turn.state);
131
+ sendToCandidate(turn.candidateSafeXml, turn.state);
132
+ ```
133
+
134
+ Deliver both artifacts to the player on each adaptive turn:
135
+
136
+ - `candidateSafeXml` — server-materialized presentation for the current turn (outcome-visible feedback, secrets stripped)
137
+ - `state` — authoritative `qti3.attempt-state.v1` with trusted responses, outcomes, and interaction state
138
+
139
+ ```ts
140
+ await player.loadXml(turn.candidateSafeXml);
141
+ player.restore(turn.state);
142
+ ```
143
+
144
+ Refresh turns with no new submission still return updated materialized XML derived from restored outcomes, so a resume flow should load both values again even when the candidate does not submit a new response.
145
+
146
+ The static delivery redactor still fails closed for adaptive response processing. Use
147
+ `processQtiAdaptiveItemTurn()` when a host needs a server-materialized candidate view
148
+ and updated authoritative outcomes for the next adaptive turn. The low-level adaptive-turn
149
+ redactor is internal to the core package; hosts should not call it directly.
150
+
112
151
  ## Scope
113
152
 
114
153
  - Parse QTI XML into a typed item model.
@@ -0,0 +1,14 @@
1
+ import { type QtiDeliverySecurityAnalysis } from "./delivery-redaction.js";
2
+ import type { QtiDiagnostic, QtiValue } from "./types.js";
3
+ export interface QtiAdaptiveCandidateMaterializationInput {
4
+ itemXml: string;
5
+ outcomes: Record<string, QtiValue>;
6
+ }
7
+ export interface QtiAdaptiveCandidateMaterializationResult {
8
+ ok: boolean;
9
+ diagnostics: QtiDiagnostic[];
10
+ analysis: QtiDeliverySecurityAnalysis;
11
+ xml?: string | undefined;
12
+ }
13
+ export declare function materializeAdaptiveCandidateView(input: QtiAdaptiveCandidateMaterializationInput): QtiAdaptiveCandidateMaterializationResult;
14
+ //# sourceMappingURL=adaptive-turn-materializer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adaptive-turn-materializer.d.ts","sourceRoot":"","sources":["../src/adaptive-turn-materializer.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,2BAA2B,EAEjC,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAW1D,MAAM,WAAW,wCAAwC;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,yCAAyC;IACxD,EAAE,EAAE,OAAO,CAAC;IACZ,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,QAAQ,EAAE,2BAA2B,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,wCAAwC,GAC9C,yCAAyC,CAE3C"}
@@ -0,0 +1,56 @@
1
+ import { FORBIDDEN_DELIVERY_SECRET_ELEMENT_NAMES, isDeclarationDefaultValue, redactDeliveryXml, } from "./delivery-redaction.js";
2
+ import { qtiValueToString } from "./value-format.js";
3
+ const UNSUPPORTED_MATERIALIZATION_ELEMENT_NAMES = new Set([
4
+ "template-processing",
5
+ "set-correct-response",
6
+ ]);
7
+ const FEEDBACK_ELEMENT_NAMES = new Set(["feedback-inline", "feedback-block", "modal-feedback"]);
8
+ export function materializeAdaptiveCandidateView(input) {
9
+ return redactDeliveryXml(input.itemXml, adaptiveMaterializationPolicy(input.outcomes));
10
+ }
11
+ function shouldPreserveFeedback(node, outcomes) {
12
+ if (node.attributes["show-hide"] === "hide")
13
+ return false;
14
+ const identifier = node.attributes.identifier;
15
+ const outcomeIdentifier = node.attributes["outcome-identifier"];
16
+ if (!identifier || !outcomeIdentifier)
17
+ return false;
18
+ const outcome = outcomes[outcomeIdentifier] ?? null;
19
+ if (Array.isArray(outcome))
20
+ return outcome.includes(identifier);
21
+ return qtiValueToString(outcome) === identifier;
22
+ }
23
+ function isFeedbackElement(normalizedName) {
24
+ return FEEDBACK_ELEMENT_NAMES.has(normalizedName);
25
+ }
26
+ function adaptiveMaterializationPolicy(outcomes) {
27
+ return {
28
+ isForbiddenElement(node, normalizedName) {
29
+ if (isFeedbackElement(normalizedName))
30
+ return !shouldPreserveFeedback(node, outcomes);
31
+ return (FORBIDDEN_DELIVERY_SECRET_ELEMENT_NAMES.has(normalizedName) ||
32
+ isDeclarationDefaultValue(node, normalizedName));
33
+ },
34
+ unsupportedFinding(node, normalizedName) {
35
+ if (!UNSUPPORTED_MATERIALIZATION_ELEMENT_NAMES.has(normalizedName))
36
+ return null;
37
+ return {
38
+ kind: "unsupported-secure-delivery-element",
39
+ qtiName: node.name,
40
+ localName: node.localName,
41
+ message: `${node.name} is not supported by adaptive candidate materialization v1.`,
42
+ source: node.source,
43
+ };
44
+ },
45
+ diagnosticCodeForFinding,
46
+ forbiddenElementMessage(node) {
47
+ return `${node.name} exposes answer keys, scoring, mapping, feedback, or solution information during adaptive delivery.`;
48
+ },
49
+ };
50
+ }
51
+ function diagnosticCodeForFinding(kind) {
52
+ return kind === "unsupported-secure-delivery-element"
53
+ ? "adaptiveTurn.materialization.unsupported"
54
+ : "adaptiveTurn.materialization.forbiddenElement";
55
+ }
56
+ //# sourceMappingURL=adaptive-turn-materializer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adaptive-turn-materializer.js","sourceRoot":"","sources":["../src/adaptive-turn-materializer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uCAAuC,EACvC,yBAAyB,EACzB,iBAAiB,GAIlB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,MAAM,yCAAyC,GAAG,IAAI,GAAG,CAAC;IACxD,qBAAqB;IACrB,sBAAsB;CACvB,CAAC,CAAC;AAEH,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC,CAAC,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAchG,MAAM,UAAU,gCAAgC,CAC9C,KAA+C;IAE/C,OAAO,iBAAiB,CAAC,KAAK,CAAC,OAAO,EAAE,6BAA6B,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;AACzF,CAAC;AAED,SAAS,sBAAsB,CAAC,IAAa,EAAE,QAAkC;IAC/E,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;IAC9C,MAAM,iBAAiB,GAAG,IAAI,CAAC,UAAU,CAAC,oBAAoB,CAAC,CAAC;IAChE,IAAI,CAAC,UAAU,IAAI,CAAC,iBAAiB;QAAE,OAAO,KAAK,CAAC;IAEpD,MAAM,OAAO,GAAG,QAAQ,CAAC,iBAAiB,CAAC,IAAI,IAAI,CAAC;IACpD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAChE,OAAO,gBAAgB,CAAC,OAAO,CAAC,KAAK,UAAU,CAAC;AAClD,CAAC;AAED,SAAS,iBAAiB,CAAC,cAAsB;IAC/C,OAAO,sBAAsB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AACpD,CAAC;AAED,SAAS,6BAA6B,CACpC,QAAkC;IAElC,OAAO;QACL,kBAAkB,CAAC,IAAI,EAAE,cAAc;YACrC,IAAI,iBAAiB,CAAC,cAAc,CAAC;gBAAE,OAAO,CAAC,sBAAsB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YACtF,OAAO,CACL,uCAAuC,CAAC,GAAG,CAAC,cAAc,CAAC;gBAC3D,yBAAyB,CAAC,IAAI,EAAE,cAAc,CAAC,CAChD,CAAC;QACJ,CAAC;QACD,kBAAkB,CAAC,IAAI,EAAE,cAAc;YACrC,IAAI,CAAC,yCAAyC,CAAC,GAAG,CAAC,cAAc,CAAC;gBAAE,OAAO,IAAI,CAAC;YAChF,OAAO;gBACL,IAAI,EAAE,qCAAqC;gBAC3C,OAAO,EAAE,IAAI,CAAC,IAAI;gBAClB,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,OAAO,EAAE,GAAG,IAAI,CAAC,IAAI,6DAA6D;gBAClF,MAAM,EAAE,IAAI,CAAC,MAAM;aACpB,CAAC;QACJ,CAAC;QACD,wBAAwB;QACxB,uBAAuB,CAAC,IAAI;YAC1B,OAAO,GAAG,IAAI,CAAC,IAAI,qGAAqG,CAAC;QAC3H,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,wBAAwB,CAAC,IAAoC;IACpE,OAAO,IAAI,KAAK,qCAAqC;QACnD,CAAC,CAAC,0CAA0C;QAC5C,CAAC,CAAC,+CAA+C,CAAC;AACtD,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { QtiAttemptStateV1, QtiDiagnostic, QtiValue } from "./types.js";
2
+ import { type QtiTrustedItemParseOptions, type QtiTrustedResponseApplication, type QtiTrustedResponseInput } from "./trusted-item-session.js";
3
+ export type QtiAdaptiveTurnResponseInput = QtiTrustedResponseInput;
4
+ export interface QtiAdaptiveTurnInput extends QtiTrustedItemParseOptions, QtiTrustedResponseApplication {
5
+ itemXml: string;
6
+ /**
7
+ * Prior attempt state from a previous adaptive turn. Non-conforming values produce
8
+ * `adaptiveTurn.state.value` diagnostics instead of being silently accepted.
9
+ */
10
+ priorState?: unknown;
11
+ }
12
+ export interface QtiAdaptiveTurnResult {
13
+ ok: boolean;
14
+ diagnostics: QtiDiagnostic[];
15
+ state: QtiAttemptStateV1 | null;
16
+ responses: Record<string, QtiValue>;
17
+ outcomes: Record<string, QtiValue>;
18
+ score: number | null;
19
+ completionStatus: QtiValue;
20
+ candidateSafeXml?: string | undefined;
21
+ }
22
+ export declare function processQtiAdaptiveItemTurn(input: QtiAdaptiveTurnInput): QtiAdaptiveTurnResult;
23
+ //# sourceMappingURL=adaptive-turn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adaptive-turn.d.ts","sourceRoot":"","sources":["../src/adaptive-turn.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,6BAA6B,EAClC,KAAK,uBAAuB,EAC7B,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,4BAA4B,GAAG,uBAAuB,CAAC;AAEnE,MAAM,WAAW,oBACf,SAAQ,0BAA0B,EAAE,6BAA6B;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,OAAO,CAAC;IACZ,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACpC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACnC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,EAAE,QAAQ,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,oBAAoB,GAAG,qBAAqB,CA0D7F"}
@@ -0,0 +1,60 @@
1
+ import { materializeAdaptiveCandidateView } from "./adaptive-turn-materializer.js";
2
+ import { readPriorAttemptState, runTrustedItemSession, } from "./trusted-item-session.js";
3
+ export function processQtiAdaptiveItemTurn(input) {
4
+ const priorStateResult = readPriorAttemptState(input.priorState, "adaptiveTurn");
5
+ if (priorStateResult.diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
6
+ return failed(priorStateResult.diagnostics);
7
+ }
8
+ const sessionResult = runTrustedItemSession({
9
+ itemXml: input.itemXml,
10
+ diagnosticPrefix: "adaptiveTurn",
11
+ allowedUndeclaredResponseIdentifiers: input.allowedUndeclaredResponseIdentifiers,
12
+ priorState: priorStateResult.state,
13
+ submission: {
14
+ trustedResponses: input.trustedResponses,
15
+ trustedInteractionStates: input.trustedInteractionStates,
16
+ },
17
+ scoring: "onSubmission",
18
+ requireNumericScore: true,
19
+ });
20
+ if (!sessionResult.ok) {
21
+ return failed(sessionResult.diagnostics, sessionResult.state, sessionResult.outcomes, sessionResult.score);
22
+ }
23
+ const delivery = materializeAdaptiveCandidateView({
24
+ itemXml: input.itemXml,
25
+ outcomes: sessionResult.outcomes,
26
+ });
27
+ const deliveryDiagnostics = [...sessionResult.diagnostics, ...delivery.diagnostics];
28
+ if (!delivery.ok || !delivery.xml) {
29
+ return failed([
30
+ ...deliveryDiagnostics,
31
+ {
32
+ code: "adaptiveTurn.delivery.materialization",
33
+ severity: "error",
34
+ message: "Adaptive turn did not produce candidate-safe item XML.",
35
+ },
36
+ ], sessionResult.state, sessionResult.outcomes, sessionResult.score);
37
+ }
38
+ return {
39
+ ok: true,
40
+ diagnostics: deliveryDiagnostics,
41
+ state: sessionResult.state,
42
+ responses: sessionResult.responses,
43
+ outcomes: sessionResult.outcomes,
44
+ score: sessionResult.score,
45
+ completionStatus: sessionResult.outcomes.completionStatus ?? null,
46
+ candidateSafeXml: delivery.xml,
47
+ };
48
+ }
49
+ function failed(diagnostics, state = null, outcomes = {}, score = null) {
50
+ return {
51
+ ok: false,
52
+ diagnostics,
53
+ state,
54
+ responses: state?.responses ?? {},
55
+ outcomes,
56
+ score,
57
+ completionStatus: outcomes.completionStatus ?? null,
58
+ };
59
+ }
60
+ //# sourceMappingURL=adaptive-turn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adaptive-turn.js","sourceRoot":"","sources":["../src/adaptive-turn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gCAAgC,EAAE,MAAM,iCAAiC,CAAC;AAEnF,OAAO,EACL,qBAAqB,EACrB,qBAAqB,GAItB,MAAM,2BAA2B,CAAC;AAyBnC,MAAM,UAAU,0BAA0B,CAAC,KAA2B;IACpE,MAAM,gBAAgB,GAAG,qBAAqB,CAAC,KAAK,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;IACjF,IAAI,gBAAgB,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,KAAK,OAAO,CAAC,EAAE,CAAC;QACvF,OAAO,MAAM,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,aAAa,GAAG,qBAAqB,CAAC;QAC1C,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,gBAAgB,EAAE,cAAc;QAChC,oCAAoC,EAAE,KAAK,CAAC,oCAAoC;QAChF,UAAU,EAAE,gBAAgB,CAAC,KAAK;QAClC,UAAU,EAAE;YACV,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;YACxC,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;SACzD;QACD,OAAO,EAAE,cAAc;QACvB,mBAAmB,EAAE,IAAI;KAC1B,CAAC,CAAC;IACH,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC;QACtB,OAAO,MAAM,CACX,aAAa,CAAC,WAAW,EACzB,aAAa,CAAC,KAAK,EACnB,aAAa,CAAC,QAAQ,EACtB,aAAa,CAAC,KAAK,CACpB,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,gCAAgC,CAAC;QAChD,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,QAAQ,EAAE,aAAa,CAAC,QAAQ;KACjC,CAAC,CAAC;IACH,MAAM,mBAAmB,GAAG,CAAC,GAAG,aAAa,CAAC,WAAW,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IACpF,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;QAClC,OAAO,MAAM,CACX;YACE,GAAG,mBAAmB;YACtB;gBACE,IAAI,EAAE,uCAAuC;gBAC7C,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,wDAAwD;aAClE;SACF,EACD,aAAa,CAAC,KAAK,EACnB,aAAa,CAAC,QAAQ,EACtB,aAAa,CAAC,KAAK,CACpB,CAAC;IACJ,CAAC;IAED,OAAO;QACL,EAAE,EAAE,IAAI;QACR,WAAW,EAAE,mBAAmB;QAChC,KAAK,EAAE,aAAa,CAAC,KAAK;QAC1B,SAAS,EAAE,aAAa,CAAC,SAAS;QAClC,QAAQ,EAAE,aAAa,CAAC,QAAQ;QAChC,KAAK,EAAE,aAAa,CAAC,KAAK;QAC1B,gBAAgB,EAAE,aAAa,CAAC,QAAQ,CAAC,gBAAgB,IAAI,IAAI;QACjE,gBAAgB,EAAE,QAAQ,CAAC,GAAG;KAC/B,CAAC;AACJ,CAAC;AAED,SAAS,MAAM,CACb,WAA4B,EAC5B,QAAkC,IAAI,EACtC,WAAqC,EAAE,EACvC,QAAuB,IAAI;IAE3B,OAAO;QACL,EAAE,EAAE,KAAK;QACT,WAAW;QACX,KAAK;QACL,SAAS,EAAE,KAAK,EAAE,SAAS,IAAI,EAAE;QACjC,QAAQ;QACR,KAAK;QACL,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB,IAAI,IAAI;KACpD,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"asset-url.d.ts","sourceRoot":"","sources":["../src/asset-url.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAQ3D"}
1
+ {"version":3,"file":"asset-url.d.ts","sourceRoot":"","sources":["../src/asset-url.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAS3D"}
package/dist/asset-url.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export function isResolvableAssetUrl(value) {
2
2
  return (!value.startsWith("#") &&
3
+ !value.startsWith("/") &&
3
4
  !value.startsWith("data:") &&
4
5
  !value.startsWith("blob:") &&
5
6
  !value.startsWith("http://") &&
@@ -1 +1 @@
1
- {"version":3,"file":"asset-url.js","sourceRoot":"","sources":["../src/asset-url.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAO,CACL,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QACtB,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC;QAC1B,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC;QAC1B,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC;QAC5B,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,CAC9B,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"asset-url.js","sourceRoot":"","sources":["../src/asset-url.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAO,CACL,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QACtB,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QACtB,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC;QAC1B,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC;QAC1B,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC;QAC5B,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,CAC9B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,50 @@
1
+ import type { QtiDiagnostic, QtiSourceLocation } from "./types.js";
2
+ import { type XmlNode } from "./xml.js";
3
+ export declare const DEFAULT_VALUE_DECLARATION_ELEMENT_NAMES: Set<string>;
4
+ export declare const FORBIDDEN_DELIVERY_SECRET_ELEMENT_NAMES: Set<string>;
5
+ export type QtiDeliverySecurityFindingKind = "forbidden-delivery-element" | "unsupported-secure-delivery-element" | "unsupported-adaptive-response-processing";
6
+ export interface QtiDeliverySecurityFinding {
7
+ kind: QtiDeliverySecurityFindingKind;
8
+ qtiName: string;
9
+ localName: string;
10
+ message: string;
11
+ source?: QtiSourceLocation | undefined;
12
+ }
13
+ export interface QtiDeliverySecurityAnalysis {
14
+ diagnostics: QtiDiagnostic[];
15
+ findings: QtiDeliverySecurityFinding[];
16
+ /** True when this exact XML contains no known answer/scoring/feedback delivery leaks. */
17
+ deliverySafe: boolean;
18
+ /** True when secure-delivery redaction can be attempted for this XML. */
19
+ secureDeliverySupported: boolean;
20
+ }
21
+ export interface ParsedDeliveryXml {
22
+ root: XmlNode | undefined;
23
+ diagnostics: QtiDiagnostic[];
24
+ }
25
+ export interface DeliveryRedactionPolicy {
26
+ isForbiddenElement(node: XmlNode, normalizedName: string): boolean;
27
+ unsupportedFinding?(node: XmlNode, normalizedName: string, nodes: readonly XmlNode[]): QtiDeliverySecurityFinding | null;
28
+ diagnosticCodeForFinding(kind: QtiDeliverySecurityFindingKind): string;
29
+ forbiddenElementMessage(node: XmlNode): string;
30
+ }
31
+ export interface RedactedDeliveryXmlResult {
32
+ ok: boolean;
33
+ diagnostics: QtiDiagnostic[];
34
+ analysis: QtiDeliverySecurityAnalysis;
35
+ xml?: string | undefined;
36
+ }
37
+ export declare function parseDeliveryXml(xml: string): ParsedDeliveryXml;
38
+ export declare function analyzeDeliveryXml(parsed: ParsedDeliveryXml, policy: DeliveryRedactionPolicy): QtiDeliverySecurityAnalysis;
39
+ export declare function redactDeliveryXml(xml: string, policy: DeliveryRedactionPolicy): RedactedDeliveryXmlResult;
40
+ export declare function readRedactionRanges(nodes: readonly XmlNode[], shouldRedact: (node: XmlNode, normalizedName: string) => boolean): RedactionRange[];
41
+ export declare function sourceRangeFor(node: XmlNode): RedactionRange[];
42
+ export declare function removeSourceRanges(xml: string, ranges: readonly RedactionRange[]): string;
43
+ export declare function normalizedQtiElementName(localName: string): string;
44
+ export declare function parseXmlBoolean(value: string | undefined): boolean | undefined;
45
+ export declare function isDeclarationDefaultValue(node: XmlNode, normalizedName: string): boolean;
46
+ export interface RedactionRange {
47
+ startOffset: number;
48
+ endOffset: number;
49
+ }
50
+ //# sourceMappingURL=delivery-redaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-redaction.d.ts","sourceRoot":"","sources":["../src/delivery-redaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,EAA6B,KAAK,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnE,eAAO,MAAM,uCAAuC,aAIlD,CAAC;AAEH,eAAO,MAAM,uCAAuC,aAOlD,CAAC;AAEH,MAAM,MAAM,8BAA8B,GACtC,4BAA4B,GAC5B,qCAAqC,GACrC,0CAA0C,CAAC;AAE/C,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,8BAA8B,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,QAAQ,EAAE,0BAA0B,EAAE,CAAC;IACvC,yFAAyF;IACzF,YAAY,EAAE,OAAO,CAAC;IACtB,yEAAyE;IACzE,uBAAuB,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,WAAW,EAAE,aAAa,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,kBAAkB,CAAC,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC;IACnE,kBAAkB,CAAC,CACjB,IAAI,EAAE,OAAO,EACb,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,SAAS,OAAO,EAAE,GACxB,0BAA0B,GAAG,IAAI,CAAC;IACrC,wBAAwB,CAAC,IAAI,EAAE,8BAA8B,GAAG,MAAM,CAAC;IACvE,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;CAChD;AAED,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,OAAO,CAAC;IACZ,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,QAAQ,EAAE,2BAA2B,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CA8B/D;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,iBAAiB,EACzB,MAAM,EAAE,uBAAuB,GAC9B,2BAA2B,CA2C7B;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,uBAAuB,GAC9B,yBAAyB,CAuC3B;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,SAAS,OAAO,EAAE,EACzB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,KAAK,OAAO,GAC/D,cAAc,EAAE,CAOlB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,EAAE,CAI9D;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAQzF;AAED,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGlE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAM9E;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAOxF;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB"}
@@ -0,0 +1,176 @@
1
+ import { descendants, parseXmlTree } from "./xml.js";
2
+ export const DEFAULT_VALUE_DECLARATION_ELEMENT_NAMES = new Set([
3
+ "response-declaration",
4
+ "outcome-declaration",
5
+ "template-declaration",
6
+ ]);
7
+ export const FORBIDDEN_DELIVERY_SECRET_ELEMENT_NAMES = new Set([
8
+ "correct-response",
9
+ "mapping",
10
+ "area-mapping",
11
+ "match-table",
12
+ "interpolation-table",
13
+ "response-processing",
14
+ ]);
15
+ export function parseDeliveryXml(xml) {
16
+ let parsed;
17
+ try {
18
+ parsed = parseXmlTree(xml);
19
+ }
20
+ catch (error) {
21
+ return {
22
+ root: undefined,
23
+ diagnostics: [
24
+ {
25
+ code: "xml.parse",
26
+ severity: "error",
27
+ message: error instanceof Error ? error.message : String(error),
28
+ },
29
+ ],
30
+ };
31
+ }
32
+ const diagnostics = parsed.errors.map((error) => ({
33
+ code: "xml.parse",
34
+ severity: "error",
35
+ message: error.message,
36
+ }));
37
+ if (!parsed.root) {
38
+ diagnostics.push({
39
+ code: "xml.empty",
40
+ severity: "error",
41
+ message: "No XML root element was found.",
42
+ });
43
+ }
44
+ return { root: parsed.root, diagnostics };
45
+ }
46
+ export function analyzeDeliveryXml(parsed, policy) {
47
+ const diagnostics = [...parsed.diagnostics];
48
+ const findings = [];
49
+ if (parsed.root) {
50
+ const nodes = [parsed.root, ...descendants(parsed.root, () => true)];
51
+ for (const node of nodes) {
52
+ const normalizedName = normalizedQtiElementName(node.localName);
53
+ if (policy.isForbiddenElement(node, normalizedName)) {
54
+ findings.push({
55
+ kind: "forbidden-delivery-element",
56
+ qtiName: node.name,
57
+ localName: node.localName,
58
+ message: policy.forbiddenElementMessage(node),
59
+ source: node.source,
60
+ });
61
+ }
62
+ const unsupportedFinding = policy.unsupportedFinding?.(node, normalizedName, nodes);
63
+ if (unsupportedFinding)
64
+ findings.push(unsupportedFinding);
65
+ }
66
+ }
67
+ diagnostics.push(...findings.map((finding) => findingToDiagnostic(finding, (kind) => policy.diagnosticCodeForFinding(kind))));
68
+ const parseOk = parsed.diagnostics.every((diagnostic) => diagnostic.severity !== "error");
69
+ return {
70
+ diagnostics,
71
+ findings,
72
+ deliverySafe: parseOk && !findings.some((finding) => finding.kind === "forbidden-delivery-element"),
73
+ secureDeliverySupported: parseOk &&
74
+ !findings.some((finding) => finding.kind === "unsupported-secure-delivery-element" ||
75
+ finding.kind === "unsupported-adaptive-response-processing"),
76
+ };
77
+ }
78
+ export function redactDeliveryXml(xml, policy) {
79
+ const parsed = parseDeliveryXml(xml);
80
+ const analysis = analyzeDeliveryXml(parsed, policy);
81
+ if (!analysis.secureDeliverySupported) {
82
+ return {
83
+ ok: false,
84
+ diagnostics: analysis.diagnostics,
85
+ analysis,
86
+ };
87
+ }
88
+ if (!parsed.root) {
89
+ return {
90
+ ok: false,
91
+ diagnostics: parsed.diagnostics,
92
+ analysis,
93
+ };
94
+ }
95
+ const redactionRanges = readRedactionRanges([parsed.root, ...descendants(parsed.root, () => true)], (node, normalizedName) => policy.isForbiddenElement(node, normalizedName));
96
+ const redactedXml = removeSourceRanges(xml, redactionRanges);
97
+ const redactedAnalysis = analyzeDeliveryXml(parseDeliveryXml(redactedXml), policy);
98
+ if (!redactedAnalysis.deliverySafe || !redactedAnalysis.secureDeliverySupported) {
99
+ return {
100
+ ok: false,
101
+ diagnostics: [...analysis.diagnostics, ...redactedAnalysis.diagnostics],
102
+ analysis: redactedAnalysis,
103
+ };
104
+ }
105
+ return {
106
+ ok: true,
107
+ xml: redactedXml,
108
+ diagnostics: redactedAnalysis.diagnostics,
109
+ analysis: redactedAnalysis,
110
+ };
111
+ }
112
+ export function readRedactionRanges(nodes, shouldRedact) {
113
+ const ranges = nodes.flatMap((node) => {
114
+ const normalizedName = normalizedQtiElementName(node.localName);
115
+ if (!shouldRedact(node, normalizedName))
116
+ return [];
117
+ return sourceRangeFor(node);
118
+ });
119
+ return mergeSourceRanges(ranges);
120
+ }
121
+ export function sourceRangeFor(node) {
122
+ const endOffset = node.sourceRange.endOffset;
123
+ if (node.sourceRange.startOffset < 0 || endOffset === undefined)
124
+ return [];
125
+ return [{ startOffset: node.sourceRange.startOffset, endOffset }];
126
+ }
127
+ export function removeSourceRanges(xml, ranges) {
128
+ let output = "";
129
+ let cursor = 0;
130
+ for (const range of ranges) {
131
+ output += xml.slice(cursor, range.startOffset);
132
+ cursor = range.endOffset;
133
+ }
134
+ return output + xml.slice(cursor);
135
+ }
136
+ export function normalizedQtiElementName(localName) {
137
+ const lower = localName.toLowerCase();
138
+ return lower.startsWith("qti-") ? lower.slice("qti-".length) : lower;
139
+ }
140
+ export function parseXmlBoolean(value) {
141
+ if (value === undefined)
142
+ return undefined;
143
+ const normalized = value.trim().toLowerCase();
144
+ if (normalized === "true" || normalized === "1")
145
+ return true;
146
+ if (normalized === "false" || normalized === "0")
147
+ return false;
148
+ return undefined;
149
+ }
150
+ export function isDeclarationDefaultValue(node, normalizedName) {
151
+ return (normalizedName === "default-value" &&
152
+ DEFAULT_VALUE_DECLARATION_ELEMENT_NAMES.has(normalizedQtiElementName(node.parent?.localName ?? "")));
153
+ }
154
+ function mergeSourceRanges(ranges) {
155
+ const sorted = [...ranges].sort((left, right) => left.startOffset - right.startOffset);
156
+ const merged = [];
157
+ for (const range of sorted) {
158
+ const last = merged.at(-1);
159
+ if (last && range.startOffset <= last.endOffset) {
160
+ last.endOffset = Math.max(last.endOffset, range.endOffset);
161
+ continue;
162
+ }
163
+ merged.push({ ...range });
164
+ }
165
+ return merged;
166
+ }
167
+ function findingToDiagnostic(finding, diagnosticCodeForFinding) {
168
+ return {
169
+ code: diagnosticCodeForFinding(finding.kind),
170
+ severity: "error",
171
+ message: finding.message,
172
+ path: finding.source?.path,
173
+ source: finding.source,
174
+ };
175
+ }
176
+ //# sourceMappingURL=delivery-redaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-redaction.js","sourceRoot":"","sources":["../src/delivery-redaction.ts"],"names":[],"mappings":"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"}
@@ -1,23 +1,8 @@
1
- import type { QtiDiagnostic, QtiSourceLocation } from "./types.js";
2
- export type QtiDeliverySecurityFindingKind = "forbidden-delivery-element" | "unsupported-secure-delivery-element" | "unsupported-adaptive-response-processing";
3
- export interface QtiDeliverySecurityFinding {
4
- kind: QtiDeliverySecurityFindingKind;
5
- qtiName: string;
6
- localName: string;
7
- message: string;
8
- source?: QtiSourceLocation | undefined;
9
- }
10
- export interface QtiDeliverySecurityAnalysis {
11
- diagnostics: QtiDiagnostic[];
12
- findings: QtiDeliverySecurityFinding[];
13
- /** True when this exact XML contains no known answer/scoring/feedback delivery leaks. */
14
- deliverySafe: boolean;
15
- /** True when secure-delivery redaction can be attempted for this XML. */
16
- secureDeliverySupported: boolean;
17
- }
1
+ import { type QtiDeliverySecurityAnalysis } from "./delivery-redaction.js";
2
+ export type { QtiDeliverySecurityAnalysis, QtiDeliverySecurityFinding, QtiDeliverySecurityFindingKind, } from "./delivery-redaction.js";
18
3
  export interface QtiDeliverySafeXmlResult {
19
4
  ok: boolean;
20
- diagnostics: QtiDiagnostic[];
5
+ diagnostics: QtiDeliverySecurityAnalysis["diagnostics"];
21
6
  analysis: QtiDeliverySecurityAnalysis;
22
7
  xml?: string | undefined;
23
8
  }
@@ -1 +1 @@
1
- {"version":3,"file":"delivery-security.d.ts","sourceRoot":"","sources":["../src/delivery-security.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AA0BnE,MAAM,MAAM,8BAA8B,GACtC,4BAA4B,GAC5B,qCAAqC,GACrC,0CAA0C,CAAC;AAE/C,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,8BAA8B,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,QAAQ,EAAE,0BAA0B,EAAE,CAAC;IACvC,yFAAyF;IACzF,YAAY,EAAE,OAAO,CAAC;IACtB,yEAAyE;IACzE,uBAAuB,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,OAAO,CAAC;IACZ,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,QAAQ,EAAE,2BAA2B,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,2BAA2B,CAGnF;AAkED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,wBAAwB,CAuC7E"}
1
+ {"version":3,"file":"delivery-security.d.ts","sourceRoot":"","sources":["../src/delivery-security.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,2BAA2B,EAEjC,MAAM,yBAAyB,CAAC;AAGjC,YAAY,EACV,2BAA2B,EAC3B,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,yBAAyB,CAAC;AAcjC,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,OAAO,CAAC;IACZ,WAAW,EAAE,2BAA2B,CAAC,aAAa,CAAC,CAAC;IACxD,QAAQ,EAAE,2BAA2B,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,2BAA2B,CAEnF;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,wBAAwB,CAE7E"}