@longsightgroup/qti3-core 0.9.1 → 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.
- package/README.md +39 -0
- package/dist/adaptive-turn-materializer.d.ts +14 -0
- package/dist/adaptive-turn-materializer.d.ts.map +1 -0
- package/dist/adaptive-turn-materializer.js +56 -0
- package/dist/adaptive-turn-materializer.js.map +1 -0
- package/dist/adaptive-turn.d.ts +23 -0
- package/dist/adaptive-turn.d.ts.map +1 -0
- package/dist/adaptive-turn.js +60 -0
- package/dist/adaptive-turn.js.map +1 -0
- package/dist/delivery-redaction.d.ts +50 -0
- package/dist/delivery-redaction.d.ts.map +1 -0
- package/dist/delivery-redaction.js +176 -0
- package/dist/delivery-redaction.js.map +1 -0
- package/dist/delivery-security.d.ts +3 -18
- package/dist/delivery-security.d.ts.map +1 -1
- package/dist/delivery-security.js +35 -189
- package/dist/delivery-security.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/server-scoring.d.ts +4 -14
- package/dist/server-scoring.d.ts.map +1 -1
- package/dist/server-scoring.js +19 -157
- package/dist/server-scoring.js.map +1 -1
- package/dist/support.d.ts.map +1 -1
- package/dist/support.js +8 -4
- package/dist/support.js.map +1 -1
- package/dist/trusted-item-session.d.ts +49 -0
- package/dist/trusted-item-session.d.ts.map +1 -0
- package/dist/trusted-item-session.js +260 -0
- package/dist/trusted-item-session.js.map +1 -0
- package/package.json +4 -2
- package/src/adaptive-turn-materializer.ts +85 -0
- package/src/adaptive-turn.ts +109 -0
- package/src/delivery-redaction.ts +268 -0
- package/src/delivery-security.ts +59 -245
- package/src/index.ts +7 -0
- package/src/server-scoring.ts +30 -226
- package/src/support.ts +9 -4
- package/src/trusted-item-session.ts +427 -0
- package/src/interaction-test-fixtures.ts +0 -44
- 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"}
|
|
@@ -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
|
|
2
|
-
export type
|
|
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:
|
|
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,
|
|
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"}
|